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 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"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
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));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy (char *dst, const char *src, size_t count)
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble (u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second, pairing;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
455 int have_sent_ICS_logon = 0;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
507 ChessSquare FIDEArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackKnight, BlackRook }
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackKing, BlackKnight, BlackRook }
521 ChessSquare KnightmateArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524 { BlackRook, BlackMan, BlackBishop, BlackQueen,
525 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackMan, BlackFerz,
553 BlackKing, BlackMan, BlackKnight, BlackRook }
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating (char *str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine (ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions (ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
738 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 InitEngine (ChessProgramState *cps, int n)
748 { // [HGM] all engine initialiation put in a function that does one engine
752 cps->which = engineNames[n];
753 cps->maybeThinking = FALSE;
757 cps->sendDrawOffers = 1;
759 cps->program = appData.chessProgram[n];
760 cps->host = appData.host[n];
761 cps->dir = appData.directory[n];
762 cps->initString = appData.engInitString[n];
763 cps->computerString = appData.computerString[n];
764 cps->useSigint = TRUE;
765 cps->useSigterm = TRUE;
766 cps->reuse = appData.reuse[n];
767 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
768 cps->useSetboard = FALSE;
770 cps->usePing = FALSE;
773 cps->usePlayother = FALSE;
774 cps->useColors = TRUE;
775 cps->useUsermove = FALSE;
776 cps->sendICS = FALSE;
777 cps->sendName = appData.icsActive;
778 cps->sdKludge = FALSE;
779 cps->stKludge = FALSE;
780 TidyProgramName(cps->program, cps->host, cps->tidy);
782 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783 cps->analysisSupport = 2; /* detect */
784 cps->analyzing = FALSE;
785 cps->initDone = FALSE;
787 /* New features added by Tord: */
788 cps->useFEN960 = FALSE;
789 cps->useOOCastle = TRUE;
790 /* End of new features added by Tord. */
791 cps->fenOverride = appData.fenOverride[n];
793 /* [HGM] time odds: set factor for each machine */
794 cps->timeOdds = appData.timeOdds[n];
796 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797 cps->accumulateTC = appData.accumulateTC[n];
798 cps->maxNrOfSessions = 1;
803 cps->supportsNPS = UNKNOWN;
804 cps->memSize = FALSE;
805 cps->maxCores = FALSE;
806 cps->egtFormats[0] = NULLCHAR;
809 cps->optionSettings = appData.engOptions[n];
811 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812 cps->isUCI = appData.isUCI[n]; /* [AS] */
813 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815 if (appData.protocolVersion[n] > PROTOVER
816 || appData.protocolVersion[n] < 1)
821 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822 appData.protocolVersion[n]);
823 if( (len >= MSG_SIZ) && appData.debugMode )
824 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826 DisplayFatalError(buf, 0, 2);
830 cps->protocolVersion = appData.protocolVersion[n];
833 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ParseFeatures(appData.featureDefaults, cps);
837 ChessProgramState *savCps;
843 if(WaitForEngine(savCps, LoadEngine)) return;
844 CommonEngineInit(); // recalculate time odds
845 if(gameInfo.variant != StringToVariant(appData.variant)) {
846 // we changed variant when loading the engine; this forces us to reset
847 Reset(TRUE, savCps != &first);
848 EditGameEvent(); // for consistency with other path, as Reset changes mode
850 InitChessProgram(savCps, FALSE);
851 SendToProgram("force\n", savCps);
852 DisplayMessage("", "");
853 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
860 ReplaceEngine (ChessProgramState *cps, int n)
864 appData.noChessProgram = FALSE;
865 appData.clockMode = TRUE;
868 if(n) return; // only startup first engine immediately; second can wait
869 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876 static char resetOptions[] =
877 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883 FloatToFront(char **list, char *engineLine)
885 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887 if(appData.recentEngines <= 0) return;
888 TidyProgramName(engineLine, "localhost", tidy+1);
889 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890 strncpy(buf+1, *list, MSG_SIZ-50);
891 if(p = strstr(buf, tidy)) { // tidy name appears in list
892 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893 while(*p++ = *++q); // squeeze out
895 strcat(tidy, buf+1); // put list behind tidy name
896 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898 ASSIGN(*list, tidy+1);
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904 Load (ChessProgramState *cps, int i)
906 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912 appData.firstProtocolVersion = PROTOVER;
913 ParseArgsFromString(buf);
915 ReplaceEngine(cps, i);
916 FloatToFront(&appData.recentEngineList, engineLine);
920 while(q = strchr(p, SLASH)) p = q+1;
921 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922 if(engineDir[0] != NULLCHAR) {
923 ASSIGN(appData.directory[i], engineDir);
924 } else if(p != engineName) { // derive directory from engine path, when not given
926 ASSIGN(appData.directory[i], engineName);
928 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929 } else { ASSIGN(appData.directory[i], "."); }
931 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932 snprintf(command, MSG_SIZ, "%s %s", p, params);
935 ASSIGN(appData.chessProgram[i], p);
936 appData.isUCI[i] = isUCI;
937 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938 appData.hasOwnBookUCI[i] = hasBook;
939 if(!nickName[0]) useNick = FALSE;
940 if(useNick) ASSIGN(appData.pgnName[i], nickName);
944 q = firstChessProgramNames;
945 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948 quote, p, quote, appData.directory[i],
949 useNick ? " -fn \"" : "",
950 useNick ? nickName : "",
952 v1 ? " -firstProtocolVersion 1" : "",
953 hasBook ? "" : " -fNoOwnBookUCI",
954 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955 storeVariant ? " -variant " : "",
956 storeVariant ? VariantName(gameInfo.variant) : "");
957 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959 if(insert != q) insert[-1] = NULLCHAR;
960 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962 FloatToFront(&appData.recentEngineList, buf);
964 ReplaceEngine(cps, i);
970 int matched, min, sec;
972 * Parse timeControl resource
974 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975 appData.movesPerSession)) {
977 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978 DisplayFatalError(buf, 0, 2);
982 * Parse searchTime resource
984 if (*appData.searchTime != NULLCHAR) {
985 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987 searchTime = min * 60;
988 } else if (matched == 2) {
989 searchTime = min * 60 + sec;
992 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993 DisplayFatalError(buf, 0, 2);
1002 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005 GetTimeMark(&programStartTime);
1006 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007 appData.seedBase = random() + (random()<<15);
1008 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010 ClearProgramStats();
1011 programStats.ok_to_send = 1;
1012 programStats.seen_stat = 0;
1015 * Initialize game list
1021 * Internet chess server status
1023 if (appData.icsActive) {
1024 appData.matchMode = FALSE;
1025 appData.matchGames = 0;
1027 appData.noChessProgram = !appData.zippyPlay;
1029 appData.zippyPlay = FALSE;
1030 appData.zippyTalk = FALSE;
1031 appData.noChessProgram = TRUE;
1033 if (*appData.icsHelper != NULLCHAR) {
1034 appData.useTelnet = TRUE;
1035 appData.telnetProgram = appData.icsHelper;
1038 appData.zippyTalk = appData.zippyPlay = FALSE;
1041 /* [AS] Initialize pv info list [HGM] and game state */
1045 for( i=0; i<=framePtr; i++ ) {
1046 pvInfoList[i].depth = -1;
1047 boards[i][EP_STATUS] = EP_NONE;
1048 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1054 /* [AS] Adjudication threshold */
1055 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057 InitEngine(&first, 0);
1058 InitEngine(&second, 1);
1061 pairing.which = "pairing"; // pairing engine
1062 pairing.pr = NoProc;
1064 pairing.program = appData.pairingEngine;
1065 pairing.host = "localhost";
1068 if (appData.icsActive) {
1069 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1070 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071 appData.clockMode = FALSE;
1072 first.sendTime = second.sendTime = 0;
1076 /* Override some settings from environment variables, for backward
1077 compatibility. Unfortunately it's not feasible to have the env
1078 vars just set defaults, at least in xboard. Ugh.
1080 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085 if (!appData.icsActive) {
1089 /* Check for variants that are supported only in ICS mode,
1090 or not at all. Some that are accepted here nevertheless
1091 have bugs; see comments below.
1093 VariantClass variant = StringToVariant(appData.variant);
1095 case VariantBughouse: /* need four players and two boards */
1096 case VariantKriegspiel: /* need to hide pieces and move details */
1097 /* case VariantFischeRandom: (Fabien: moved below) */
1098 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099 if( (len >= MSG_SIZ) && appData.debugMode )
1100 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102 DisplayFatalError(buf, 0, 2);
1105 case VariantUnknown:
1106 case VariantLoadable:
1116 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117 if( (len >= MSG_SIZ) && appData.debugMode )
1118 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120 DisplayFatalError(buf, 0, 2);
1123 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1124 case VariantFairy: /* [HGM] TestLegality definitely off! */
1125 case VariantGothic: /* [HGM] should work */
1126 case VariantCapablanca: /* [HGM] should work */
1127 case VariantCourier: /* [HGM] initial forced moves not implemented */
1128 case VariantShogi: /* [HGM] could still mate with pawn drop */
1129 case VariantKnightmate: /* [HGM] should work */
1130 case VariantCylinder: /* [HGM] untested */
1131 case VariantFalcon: /* [HGM] untested */
1132 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133 offboard interposition not understood */
1134 case VariantNormal: /* definitely works! */
1135 case VariantWildCastle: /* pieces not automatically shuffled */
1136 case VariantNoCastle: /* pieces not automatically shuffled */
1137 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138 case VariantLosers: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantSuicide: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantGiveaway: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantTwoKings: /* should work */
1145 case VariantAtomic: /* should work except for win condition */
1146 case Variant3Check: /* should work except for win condition */
1147 case VariantShatranj: /* should work except for all win conditions */
1148 case VariantMakruk: /* should work except for draw countdown */
1149 case VariantBerolina: /* might work if TestLegality is off */
1150 case VariantCapaRandom: /* should work */
1151 case VariantJanus: /* should work */
1152 case VariantSuper: /* experimental */
1153 case VariantGreat: /* experimental, requires legality testing to be off */
1154 case VariantSChess: /* S-Chess, should work */
1155 case VariantGrand: /* should work */
1156 case VariantSpartan: /* should work */
1164 NextIntegerFromString (char ** str, long * value)
1169 while( *s == ' ' || *s == '\t' ) {
1175 if( *s >= '0' && *s <= '9' ) {
1176 while( *s >= '0' && *s <= '9' ) {
1177 *value = *value * 10 + (*s - '0');
1190 NextTimeControlFromString (char ** str, long * value)
1193 int result = NextIntegerFromString( str, &temp );
1196 *value = temp * 60; /* Minutes */
1197 if( **str == ':' ) {
1199 result = NextIntegerFromString( str, &temp );
1200 *value += temp; /* Seconds */
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210 int result = -1, type = 0; long temp, temp2;
1212 if(**str != ':') return -1; // old params remain in force!
1214 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215 if( NextIntegerFromString( str, &temp ) ) return -1;
1216 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219 /* time only: incremental or sudden-death time control */
1220 if(**str == '+') { /* increment follows; read it */
1222 if(**str == '!') type = *(*str)++; // Bronstein TC
1223 if(result = NextIntegerFromString( str, &temp2)) return -1;
1224 *inc = temp2 * 1000;
1225 if(**str == '.') { // read fraction of increment
1226 char *start = ++(*str);
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229 while(start++ < *str) temp2 /= 10;
1233 *moves = 0; *tc = temp * 1000; *incType = type;
1237 (*str)++; /* classical time control */
1238 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 { /* [HGM] get time to add from the multi-session time-control string */
1252 int incType, moves=1; /* kludge to force reading of first session */
1253 long time, increment;
1256 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260 if(movenr == -1) return time; /* last move before new session */
1261 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263 if(!moves) return increment; /* current session is incremental */
1264 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265 } while(movenr >= -1); /* try again for next session */
1267 return 0; // no new time quota on this move
1271 ParseTimeControl (char *tc, float ti, int mps)
1275 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1284 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291 snprintf(buf, MSG_SIZ, ":%s", mytc);
1293 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300 /* Parse second time control */
1303 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1311 timeControl_2 = tc2 * 1000;
1321 timeControl = tc1 * 1000;
1324 timeIncrement = ti * 1000; /* convert to ms */
1325 movesPerSession = 0;
1328 movesPerSession = mps;
1336 if (appData.debugMode) {
1337 fprintf(debugFP, "%s\n", programVersion);
1339 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341 set_cont_sequence(appData.wrapContSeq);
1342 if (appData.matchGames > 0) {
1343 appData.matchMode = TRUE;
1344 } else if (appData.matchMode) {
1345 appData.matchGames = 1;
1347 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348 appData.matchGames = appData.sameColorGames;
1349 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354 if (appData.noChessProgram || first.protocolVersion == 1) {
1357 /* kludge: allow timeout for initial "feature" commands */
1359 DisplayMessage("", _("Starting chess program"));
1360 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365 CalculateIndex (int index, int gameNr)
1366 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368 if(index > 0) return index; // fixed nmber
1369 if(index == 0) return 1;
1370 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376 LoadGameOrPosition (int gameNr)
1377 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378 if (*appData.loadGameFile != NULLCHAR) {
1379 if (!LoadGameFromFile(appData.loadGameFile,
1380 CalculateIndex(appData.loadGameIndex, gameNr),
1381 appData.loadGameFile, FALSE)) {
1382 DisplayFatalError(_("Bad game file"), 0, 1);
1385 } else if (*appData.loadPositionFile != NULLCHAR) {
1386 if (!LoadPositionFromFile(appData.loadPositionFile,
1387 CalculateIndex(appData.loadPositionIndex, gameNr),
1388 appData.loadPositionFile)) {
1389 DisplayFatalError(_("Bad position file"), 0, 1);
1397 ReserveGame (int gameNr, char resChar)
1399 FILE *tf = fopen(appData.tourneyFile, "r+");
1400 char *p, *q, c, buf[MSG_SIZ];
1401 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402 safeStrCpy(buf, lastMsg, MSG_SIZ);
1403 DisplayMessage(_("Pick new game"), "");
1404 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405 ParseArgsFromFile(tf);
1406 p = q = appData.results;
1407 if(appData.debugMode) {
1408 char *r = appData.participants;
1409 fprintf(debugFP, "results = '%s'\n", p);
1410 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411 fprintf(debugFP, "\n");
1413 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416 safeStrCpy(q, p, strlen(p) + 2);
1417 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423 fseek(tf, -(strlen(p)+4), SEEK_END);
1425 if(c != '"') // depending on DOS or Unix line endings we can be one off
1426 fseek(tf, -(strlen(p)+2), SEEK_END);
1427 else fseek(tf, -(strlen(p)+3), SEEK_END);
1428 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429 DisplayMessage(buf, "");
1430 free(p); appData.results = q;
1431 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433 int round = appData.defaultMatchGames * appData.tourneyType;
1434 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1435 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436 UnloadEngine(&first); // next game belongs to other pairing;
1437 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1443 MatchEvent (int mode)
1444 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446 if(matchMode) { // already in match mode: switch it off
1448 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451 // if(gameMode != BeginningOfGame) {
1452 // DisplayError(_("You can only start a match from the initial position."), 0);
1456 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457 /* Set up machine vs. machine match */
1459 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460 if(appData.tourneyFile[0]) {
1462 if(nextGame > appData.matchGames) {
1464 if(strchr(appData.results, '*') == NULL) {
1466 appData.tourneyCycles++;
1467 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469 NextTourneyGame(-1, &dummy);
1471 if(nextGame <= appData.matchGames) {
1472 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474 ScheduleDelayedEvent(NextMatchGame, 10000);
1479 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480 DisplayError(buf, 0);
1481 appData.tourneyFile[0] = 0;
1485 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1486 DisplayFatalError(_("Can't have a match with no chess programs"),
1491 matchGame = roundNr = 1;
1492 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499 InitBackEnd3 P((void))
1501 GameMode initialMode;
1505 InitChessProgram(&first, startedFromSetupPosition);
1507 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1508 free(programVersion);
1509 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514 if (appData.icsActive) {
1516 /* [DM] Make a console window if needed [HGM] merged ifs */
1522 if (*appData.icsCommPort != NULLCHAR)
1523 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524 appData.icsCommPort);
1526 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527 appData.icsHost, appData.icsPort);
1529 if( (len >= MSG_SIZ) && appData.debugMode )
1530 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532 DisplayFatalError(buf, err, 1);
1537 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542 } else if (appData.noChessProgram) {
1548 if (*appData.cmailGameName != NULLCHAR) {
1550 OpenLoopback(&cmailPR);
1552 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1556 DisplayMessage("", "");
1557 if (StrCaseCmp(appData.initialMode, "") == 0) {
1558 initialMode = BeginningOfGame;
1559 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566 initialMode = TwoMachinesPlay;
1567 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568 initialMode = AnalyzeFile;
1569 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570 initialMode = AnalyzeMode;
1571 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572 initialMode = MachinePlaysWhite;
1573 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574 initialMode = MachinePlaysBlack;
1575 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576 initialMode = EditGame;
1577 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578 initialMode = EditPosition;
1579 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580 initialMode = Training;
1582 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, 0, 2);
1590 if (appData.matchMode) {
1591 if(appData.tourneyFile[0]) { // start tourney from command line
1593 if(f = fopen(appData.tourneyFile, "r")) {
1594 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596 appData.clockMode = TRUE;
1598 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601 } else if (*appData.cmailGameName != NULLCHAR) {
1602 /* Set up cmail mode */
1603 ReloadCmailMsgEvent(TRUE);
1605 /* Set up other modes */
1606 if (initialMode == AnalyzeFile) {
1607 if (*appData.loadGameFile == NULLCHAR) {
1608 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1612 if (*appData.loadGameFile != NULLCHAR) {
1613 (void) LoadGameFromFile(appData.loadGameFile,
1614 appData.loadGameIndex,
1615 appData.loadGameFile, TRUE);
1616 } else if (*appData.loadPositionFile != NULLCHAR) {
1617 (void) LoadPositionFromFile(appData.loadPositionFile,
1618 appData.loadPositionIndex,
1619 appData.loadPositionFile);
1620 /* [HGM] try to make self-starting even after FEN load */
1621 /* to allow automatic setup of fairy variants with wtm */
1622 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623 gameMode = BeginningOfGame;
1624 setboardSpoiledMachineBlack = 1;
1626 /* [HGM] loadPos: make that every new game uses the setup */
1627 /* from file as long as we do not switch variant */
1628 if(!blackPlaysFirst) {
1629 startedFromPositionFile = TRUE;
1630 CopyBoard(filePosition, boards[0]);
1633 if (initialMode == AnalyzeMode) {
1634 if (appData.noChessProgram) {
1635 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638 if (appData.icsActive) {
1639 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1643 } else if (initialMode == AnalyzeFile) {
1644 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645 ShowThinkingEvent();
1647 AnalysisPeriodicEvent(1);
1648 } else if (initialMode == MachinePlaysWhite) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1654 if (appData.icsActive) {
1655 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1659 MachineWhiteEvent();
1660 } else if (initialMode == MachinePlaysBlack) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1666 if (appData.icsActive) {
1667 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1671 MachineBlackEvent();
1672 } else if (initialMode == TwoMachinesPlay) {
1673 if (appData.noChessProgram) {
1674 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1678 if (appData.icsActive) {
1679 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684 } else if (initialMode == EditGame) {
1686 } else if (initialMode == EditPosition) {
1687 EditPositionEvent();
1688 } else if (initialMode == Training) {
1689 if (*appData.loadGameFile == NULLCHAR) {
1690 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 DisplayBook(current+1);
1703 MoveHistorySet( movelist, first, last, current, pvInfoList );
1705 EvalGraphSet( first, last, current, pvInfoList );
1707 MakeEngineOutputTitle();
1711 * Establish will establish a contact to a remote host.port.
1712 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713 * used to talk to the host.
1714 * Returns 0 if okay, error code if not.
1721 if (*appData.icsCommPort != NULLCHAR) {
1722 /* Talk to the host through a serial comm port */
1723 return OpenCommPort(appData.icsCommPort, &icsPR);
1725 } else if (*appData.gateway != NULLCHAR) {
1726 if (*appData.remoteShell == NULLCHAR) {
1727 /* Use the rcmd protocol to run telnet program on a gateway host */
1728 snprintf(buf, sizeof(buf), "%s %s %s",
1729 appData.telnetProgram, appData.icsHost, appData.icsPort);
1730 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733 /* Use the rsh program to run telnet program on a gateway host */
1734 if (*appData.remoteUser == NULLCHAR) {
1735 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736 appData.gateway, appData.telnetProgram,
1737 appData.icsHost, appData.icsPort);
1739 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740 appData.remoteShell, appData.gateway,
1741 appData.remoteUser, appData.telnetProgram,
1742 appData.icsHost, appData.icsPort);
1744 return StartChildProcess(buf, "", &icsPR);
1747 } else if (appData.useTelnet) {
1748 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751 /* TCP socket interface differs somewhat between
1752 Unix and NT; handle details in the front end.
1754 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759 EscapeExpand (char *p, char *q)
1760 { // [HGM] initstring: routine to shape up string arguments
1761 while(*p++ = *q++) if(p[-1] == '\\')
1763 case 'n': p[-1] = '\n'; break;
1764 case 'r': p[-1] = '\r'; break;
1765 case 't': p[-1] = '\t'; break;
1766 case '\\': p[-1] = '\\'; break;
1767 case 0: *p = 0; return;
1768 default: p[-1] = q[-1]; break;
1773 show_bytes (FILE *fp, char *buf, int count)
1776 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777 fprintf(fp, "\\%03o", *buf & 0xff);
1786 /* Returns an errno value */
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 char buf[8192], *p, *q, *buflim;
1791 int left, newcount, outcount;
1793 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794 *appData.gateway != NULLCHAR) {
1795 if (appData.debugMode) {
1796 fprintf(debugFP, ">ICS: ");
1797 show_bytes(debugFP, message, count);
1798 fprintf(debugFP, "\n");
1800 return OutputToProcess(pr, message, count, outError);
1803 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, buf, newcount);
1813 fprintf(debugFP, "\n");
1815 outcount = OutputToProcess(pr, buf, newcount, outError);
1816 if (outcount < newcount) return -1; /* to be sure */
1823 } else if (((unsigned char) *p) == TN_IAC) {
1824 *q++ = (char) TN_IAC;
1831 if (appData.debugMode) {
1832 fprintf(debugFP, ">ICS: ");
1833 show_bytes(debugFP, buf, newcount);
1834 fprintf(debugFP, "\n");
1836 outcount = OutputToProcess(pr, buf, newcount, outError);
1837 if (outcount < newcount) return -1; /* to be sure */
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 int outError, outCount;
1845 static int gotEof = 0;
1847 /* Pass data read from player on to ICS */
1850 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851 if (outCount < count) {
1852 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854 } else if (count < 0) {
1855 RemoveInputSource(isr);
1856 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857 } else if (gotEof++ > 0) {
1858 RemoveInputSource(isr);
1859 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1865 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868 SendToICS("date\n");
1869 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1872 /* added routine for printf style output to ics */
1874 ics_printf (char *format, ...)
1876 char buffer[MSG_SIZ];
1879 va_start(args, format);
1880 vsnprintf(buffer, sizeof(buffer), format, args);
1881 buffer[sizeof(buffer)-1] = '\0';
1889 int count, outCount, outError;
1891 if (icsPR == NoProc) return;
1894 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895 if (outCount < count) {
1896 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 /* This is used for sending logon scripts to the ICS. Sending
1901 without a delay causes problems when using timestamp on ICC
1902 (at least on my machine). */
1904 SendToICSDelayed (char *s, long msdelay)
1906 int count, outCount, outError;
1908 if (icsPR == NoProc) return;
1911 if (appData.debugMode) {
1912 fprintf(debugFP, ">ICS: ");
1913 show_bytes(debugFP, s, count);
1914 fprintf(debugFP, "\n");
1916 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918 if (outCount < count) {
1919 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1924 /* Remove all highlighting escape sequences in s
1925 Also deletes any suffix starting with '('
1928 StripHighlightAndTitle (char *s)
1930 static char retbuf[MSG_SIZ];
1933 while (*s != NULLCHAR) {
1934 while (*s == '\033') {
1935 while (*s != NULLCHAR && !isalpha(*s)) s++;
1936 if (*s != NULLCHAR) s++;
1938 while (*s != NULLCHAR && *s != '\033') {
1939 if (*s == '(' || *s == '[') {
1950 /* Remove all highlighting escape sequences in s */
1952 StripHighlight (char *s)
1954 static char retbuf[MSG_SIZ];
1957 while (*s != NULLCHAR) {
1958 while (*s == '\033') {
1959 while (*s != NULLCHAR && !isalpha(*s)) s++;
1960 if (*s != NULLCHAR) s++;
1962 while (*s != NULLCHAR && *s != '\033') {
1970 char *variantNames[] = VARIANT_NAMES;
1972 VariantName (VariantClass v)
1974 return variantNames[v];
1978 /* Identify a variant from the strings the chess servers use or the
1979 PGN Variant tag names we use. */
1981 StringToVariant (char *e)
1985 VariantClass v = VariantNormal;
1986 int i, found = FALSE;
1992 /* [HGM] skip over optional board-size prefixes */
1993 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995 while( *e++ != '_');
1998 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2002 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003 if (StrCaseStr(e, variantNames[i])) {
2004 v = (VariantClass) i;
2011 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012 || StrCaseStr(e, "wild/fr")
2013 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014 v = VariantFischeRandom;
2015 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016 (i = 1, p = StrCaseStr(e, "w"))) {
2018 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2025 case 0: /* FICS only, actually */
2027 /* Castling legal even if K starts on d-file */
2028 v = VariantWildCastle;
2033 /* Castling illegal even if K & R happen to start in
2034 normal positions. */
2035 v = VariantNoCastle;
2048 /* Castling legal iff K & R start in normal positions */
2054 /* Special wilds for position setup; unclear what to do here */
2055 v = VariantLoadable;
2058 /* Bizarre ICC game */
2059 v = VariantTwoKings;
2062 v = VariantKriegspiel;
2068 v = VariantFischeRandom;
2071 v = VariantCrazyhouse;
2074 v = VariantBughouse;
2080 /* Not quite the same as FICS suicide! */
2081 v = VariantGiveaway;
2087 v = VariantShatranj;
2090 /* Temporary names for future ICC types. The name *will* change in
2091 the next xboard/WinBoard release after ICC defines it. */
2129 v = VariantCapablanca;
2132 v = VariantKnightmate;
2138 v = VariantCylinder;
2144 v = VariantCapaRandom;
2147 v = VariantBerolina;
2159 /* Found "wild" or "w" in the string but no number;
2160 must assume it's normal chess. */
2164 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165 if( (len >= MSG_SIZ) && appData.debugMode )
2166 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168 DisplayError(buf, 0);
2174 if (appData.debugMode) {
2175 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176 e, wnum, VariantName(v));
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185 advance *index beyond it, and set leftover_start to the new value of
2186 *index; else return FALSE. If pattern contains the character '*', it
2187 matches any sequence of characters not containing '\r', '\n', or the
2188 character following the '*' (if any), and the matched sequence(s) are
2189 copied into star_match.
2192 looking_at ( char *buf, int *index, char *pattern)
2194 char *bufp = &buf[*index], *patternp = pattern;
2196 char *matchp = star_match[0];
2199 if (*patternp == NULLCHAR) {
2200 *index = leftover_start = bufp - buf;
2204 if (*bufp == NULLCHAR) return FALSE;
2205 if (*patternp == '*') {
2206 if (*bufp == *(patternp + 1)) {
2208 matchp = star_match[++star_count];
2212 } else if (*bufp == '\n' || *bufp == '\r') {
2214 if (*patternp == NULLCHAR)
2219 *matchp++ = *bufp++;
2223 if (*patternp != *bufp) return FALSE;
2230 SendToPlayer (char *data, int length)
2232 int error, outCount;
2233 outCount = OutputToProcess(NoProc, data, length, &error);
2234 if (outCount < length) {
2235 DisplayFatalError(_("Error writing to display"), error, 1);
2240 PackHolding (char packed[], char *holding)
2250 switch (runlength) {
2261 sprintf(q, "%d", runlength);
2273 /* Telnet protocol requests from the front end */
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2277 unsigned char msg[3];
2278 int outCount, outError;
2280 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282 if (appData.debugMode) {
2283 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2299 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2308 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2311 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2316 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2325 if (!appData.icsActive) return;
2326 TelnetRequest(TN_DO, TN_ECHO);
2332 if (!appData.icsActive) return;
2333 TelnetRequest(TN_DONT, TN_ECHO);
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 /* put the holdings sent to us by the server on the board holdings area */
2340 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2344 if(gameInfo.holdingsWidth < 2) return;
2345 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346 return; // prevent overwriting by pre-board holdings
2348 if( (int)lowestPiece >= BlackPawn ) {
2351 holdingsStartRow = BOARD_HEIGHT-1;
2354 holdingsColumn = BOARD_WIDTH-1;
2355 countsColumn = BOARD_WIDTH-2;
2356 holdingsStartRow = 0;
2360 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361 board[i][holdingsColumn] = EmptySquare;
2362 board[i][countsColumn] = (ChessSquare) 0;
2364 while( (p=*holdings++) != NULLCHAR ) {
2365 piece = CharToPiece( ToUpper(p) );
2366 if(piece == EmptySquare) continue;
2367 /*j = (int) piece - (int) WhitePawn;*/
2368 j = PieceToNumber(piece);
2369 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370 if(j < 0) continue; /* should not happen */
2371 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373 board[holdingsStartRow+j*direction][countsColumn]++;
2379 VariantSwitch (Board board, VariantClass newVariant)
2381 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382 static Board oldBoard;
2384 startedFromPositionFile = FALSE;
2385 if(gameInfo.variant == newVariant) return;
2387 /* [HGM] This routine is called each time an assignment is made to
2388 * gameInfo.variant during a game, to make sure the board sizes
2389 * are set to match the new variant. If that means adding or deleting
2390 * holdings, we shift the playing board accordingly
2391 * This kludge is needed because in ICS observe mode, we get boards
2392 * of an ongoing game without knowing the variant, and learn about the
2393 * latter only later. This can be because of the move list we requested,
2394 * in which case the game history is refilled from the beginning anyway,
2395 * but also when receiving holdings of a crazyhouse game. In the latter
2396 * case we want to add those holdings to the already received position.
2400 if (appData.debugMode) {
2401 fprintf(debugFP, "Switch board from %s to %s\n",
2402 VariantName(gameInfo.variant), VariantName(newVariant));
2403 setbuf(debugFP, NULL);
2405 shuffleOpenings = 0; /* [HGM] shuffle */
2406 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2410 newWidth = 9; newHeight = 9;
2411 gameInfo.holdingsSize = 7;
2412 case VariantBughouse:
2413 case VariantCrazyhouse:
2414 newHoldingsWidth = 2; break;
2418 newHoldingsWidth = 2;
2419 gameInfo.holdingsSize = 8;
2422 case VariantCapablanca:
2423 case VariantCapaRandom:
2426 newHoldingsWidth = gameInfo.holdingsSize = 0;
2429 if(newWidth != gameInfo.boardWidth ||
2430 newHeight != gameInfo.boardHeight ||
2431 newHoldingsWidth != gameInfo.holdingsWidth ) {
2433 /* shift position to new playing area, if needed */
2434 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435 for(i=0; i<BOARD_HEIGHT; i++)
2436 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439 for(i=0; i<newHeight; i++) {
2440 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444 for(i=0; i<BOARD_HEIGHT; i++)
2445 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2449 gameInfo.boardWidth = newWidth;
2450 gameInfo.boardHeight = newHeight;
2451 gameInfo.holdingsWidth = newHoldingsWidth;
2452 gameInfo.variant = newVariant;
2453 InitDrawingSizes(-2, 0);
2454 } else gameInfo.variant = newVariant;
2455 CopyBoard(oldBoard, board); // remember correctly formatted board
2456 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2457 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 static int loggedOn = FALSE;
2462 /*-- Game start info cache: --*/
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\ ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2492 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494 if(r < minRating+100 && r >=0 ) r = minRating+100;
2495 if(r > maxRating) r = maxRating;
2496 if(tc < 1.) tc = 1.;
2497 if(tc > 95.) tc = 95.;
2498 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499 y = ((double)r - minRating)/(maxRating - minRating)
2500 * (h-vMargin-squareSize/8-1) + vMargin;
2501 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502 if(strstr(seekAdList[i], " u ")) color = 1;
2503 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504 !strstr(seekAdList[i], "bullet") &&
2505 !strstr(seekAdList[i], "blitz") &&
2506 !strstr(seekAdList[i], "standard") ) color = 2;
2507 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2512 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2514 char buf[MSG_SIZ], *ext = "";
2515 VariantClass v = StringToVariant(type);
2516 if(strstr(type, "wild")) {
2517 ext = type + 4; // append wild number
2518 if(v == VariantFischeRandom) type = "chess960"; else
2519 if(v == VariantLoadable) type = "setup"; else
2520 type = VariantName(v);
2522 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528 seekNrList[nrOfSeekAds] = nr;
2529 zList[nrOfSeekAds] = 0;
2530 seekAdList[nrOfSeekAds++] = StrSave(buf);
2531 if(plot) PlotSeekAd(nrOfSeekAds-1);
2536 EraseSeekDot (int i)
2538 int x = xList[i], y = yList[i], d=squareSize/4, k;
2539 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541 // now replot every dot that overlapped
2542 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543 int xx = xList[k], yy = yList[k];
2544 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545 DrawSeekDot(xx, yy, colorList[k]);
2550 RemoveSeekAd (int nr)
2553 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555 if(seekAdList[i]) free(seekAdList[i]);
2556 seekAdList[i] = seekAdList[--nrOfSeekAds];
2557 seekNrList[i] = seekNrList[nrOfSeekAds];
2558 ratingList[i] = ratingList[nrOfSeekAds];
2559 colorList[i] = colorList[nrOfSeekAds];
2560 tcList[i] = tcList[nrOfSeekAds];
2561 xList[i] = xList[nrOfSeekAds];
2562 yList[i] = yList[nrOfSeekAds];
2563 zList[i] = zList[nrOfSeekAds];
2564 seekAdList[nrOfSeekAds] = NULL;
2570 MatchSoughtLine (char *line)
2572 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573 int nr, base, inc, u=0; char dummy;
2575 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2580 // match: compact and save the line
2581 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2591 if(!seekGraphUp) return FALSE;
2592 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2595 DrawSeekBackground(0, 0, w, h);
2596 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604 snprintf(buf, MSG_SIZ, "%d", i);
2605 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609 for(i=1; i<100; i+=(i<10?1:5)) {
2610 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614 snprintf(buf, MSG_SIZ, "%d", i);
2615 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 static int lastDown = 0, displayed = 0, lastSecond;
2626 if(y < 0) return FALSE;
2627 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629 if(!seekGraphUp) return FALSE;
2630 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631 DrawPosition(TRUE, NULL);
2634 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635 if(click == Release || moving) return FALSE;
2637 soughtPending = TRUE;
2638 SendToICS(ics_prefix);
2639 SendToICS("sought\n"); // should this be "sought all"?
2640 } else { // issue challenge based on clicked ad
2641 int dist = 10000; int i, closest = 0, second = 0;
2642 for(i=0; i<nrOfSeekAds; i++) {
2643 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2644 if(d < dist) { dist = d; closest = i; }
2645 second += (d - zList[i] < 120); // count in-range ads
2646 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2650 second = (second > 1);
2651 if(displayed != closest || second != lastSecond) {
2652 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653 lastSecond = second; displayed = closest;
2655 if(click == Press) {
2656 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659 } // on press 'hit', only show info
2660 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662 SendToICS(ics_prefix);
2664 return TRUE; // let incoming board of started game pop down the graph
2665 } else if(click == Release) { // release 'miss' is ignored
2666 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667 if(moving == 2) { // right up-click
2668 nrOfSeekAds = 0; // refresh graph
2669 soughtPending = TRUE;
2670 SendToICS(ics_prefix);
2671 SendToICS("sought\n"); // should this be "sought all"?
2674 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675 // press miss or release hit 'pop down' seek graph
2676 seekGraphUp = FALSE;
2677 DrawPosition(TRUE, NULL);
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2695 static int started = STARTED_NONE;
2696 static char parse[20000];
2697 static int parse_pos = 0;
2698 static char buf[BUF_SIZE + 1];
2699 static int firstTime = TRUE, intfSet = FALSE;
2700 static ColorClass prevColor = ColorNormal;
2701 static int savingComment = FALSE;
2702 static int cmatch = 0; // continuation sequence match
2709 int backup; /* [DM] For zippy color lines */
2711 char talker[MSG_SIZ]; // [HGM] chat
2714 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716 if (appData.debugMode) {
2718 fprintf(debugFP, "<ICS: ");
2719 show_bytes(debugFP, data, count);
2720 fprintf(debugFP, "\n");
2724 if (appData.debugMode) { int f = forwardMostMove;
2725 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730 /* If last read ended with a partial line that we couldn't parse,
2731 prepend it to the new read and try again. */
2732 if (leftover_len > 0) {
2733 for (i=0; i<leftover_len; i++)
2734 buf[i] = buf[leftover_start + i];
2737 /* copy new characters into the buffer */
2738 bp = buf + leftover_len;
2739 buf_len=leftover_len;
2740 for (i=0; i<count; i++)
2743 if (data[i] == '\r')
2746 // join lines split by ICS?
2747 if (!appData.noJoin)
2750 Joining just consists of finding matches against the
2751 continuation sequence, and discarding that sequence
2752 if found instead of copying it. So, until a match
2753 fails, there's nothing to do since it might be the
2754 complete sequence, and thus, something we don't want
2757 if (data[i] == cont_seq[cmatch])
2760 if (cmatch == strlen(cont_seq))
2762 cmatch = 0; // complete match. just reset the counter
2765 it's possible for the ICS to not include the space
2766 at the end of the last word, making our [correct]
2767 join operation fuse two separate words. the server
2768 does this when the space occurs at the width setting.
2770 if (!buf_len || buf[buf_len-1] != ' ')
2781 match failed, so we have to copy what matched before
2782 falling through and copying this character. In reality,
2783 this will only ever be just the newline character, but
2784 it doesn't hurt to be precise.
2786 strncpy(bp, cont_seq, cmatch);
2798 buf[buf_len] = NULLCHAR;
2799 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2804 while (i < buf_len) {
2805 /* Deal with part of the TELNET option negotiation
2806 protocol. We refuse to do anything beyond the
2807 defaults, except that we allow the WILL ECHO option,
2808 which ICS uses to turn off password echoing when we are
2809 directly connected to it. We reject this option
2810 if localLineEditing mode is on (always on in xboard)
2811 and we are talking to port 23, which might be a real
2812 telnet server that will try to keep WILL ECHO on permanently.
2814 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816 unsigned char option;
2818 switch ((unsigned char) buf[++i]) {
2820 if (appData.debugMode)
2821 fprintf(debugFP, "\n<WILL ");
2822 switch (option = (unsigned char) buf[++i]) {
2824 if (appData.debugMode)
2825 fprintf(debugFP, "ECHO ");
2826 /* Reply only if this is a change, according
2827 to the protocol rules. */
2828 if (remoteEchoOption) break;
2829 if (appData.localLineEditing &&
2830 atoi(appData.icsPort) == TN_PORT) {
2831 TelnetRequest(TN_DONT, TN_ECHO);
2834 TelnetRequest(TN_DO, TN_ECHO);
2835 remoteEchoOption = TRUE;
2839 if (appData.debugMode)
2840 fprintf(debugFP, "%d ", option);
2841 /* Whatever this is, we don't want it. */
2842 TelnetRequest(TN_DONT, option);
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<WONT ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 if (appData.debugMode)
2852 fprintf(debugFP, "ECHO ");
2853 /* Reply only if this is a change, according
2854 to the protocol rules. */
2855 if (!remoteEchoOption) break;
2857 TelnetRequest(TN_DONT, TN_ECHO);
2858 remoteEchoOption = FALSE;
2861 if (appData.debugMode)
2862 fprintf(debugFP, "%d ", (unsigned char) option);
2863 /* Whatever this is, it must already be turned
2864 off, because we never agree to turn on
2865 anything non-default, so according to the
2866 protocol rules, we don't reply. */
2871 if (appData.debugMode)
2872 fprintf(debugFP, "\n<DO ");
2873 switch (option = (unsigned char) buf[++i]) {
2875 /* Whatever this is, we refuse to do it. */
2876 if (appData.debugMode)
2877 fprintf(debugFP, "%d ", option);
2878 TelnetRequest(TN_WONT, option);
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<DONT ");
2885 switch (option = (unsigned char) buf[++i]) {
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we are already not doing
2890 it, because we never agree to do anything
2891 non-default, so according to the protocol
2892 rules, we don't reply. */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<IAC ");
2899 /* Doubled IAC; pass it through */
2903 if (appData.debugMode)
2904 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905 /* Drop all other telnet commands on the floor */
2908 if (oldi > next_out)
2909 SendToPlayer(&buf[next_out], oldi - next_out);
2915 /* OK, this at least will *usually* work */
2916 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2920 if (loggedOn && !intfSet) {
2921 if (ics_type == ICS_ICC) {
2922 snprintf(str, MSG_SIZ,
2923 "/set-quietly interface %s\n/set-quietly style 12\n",
2925 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926 strcat(str, "/set-2 51 1\n/set seek 1\n");
2927 } else if (ics_type == ICS_CHESSNET) {
2928 snprintf(str, MSG_SIZ, "/style 12\n");
2930 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931 strcat(str, programVersion);
2932 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 strcat(str, "$iset nohighlight 1\n");
2938 strcat(str, "$iset lock 1\n$style 12\n");
2941 NotifyFrontendLogin();
2945 if (started == STARTED_COMMENT) {
2946 /* Accumulate characters in comment */
2947 parse[parse_pos++] = buf[i];
2948 if (buf[i] == '\n') {
2949 parse[parse_pos] = NULLCHAR;
2950 if(chattingPartner>=0) {
2952 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953 OutputChatMessage(chattingPartner, mess);
2954 chattingPartner = -1;
2955 next_out = i+1; // [HGM] suppress printing in ICS window
2957 if(!suppressKibitz) // [HGM] kibitz
2958 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960 int nrDigit = 0, nrAlph = 0, j;
2961 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963 parse[parse_pos] = NULLCHAR;
2964 // try to be smart: if it does not look like search info, it should go to
2965 // ICS interaction window after all, not to engine-output window.
2966 for(j=0; j<parse_pos; j++) { // count letters and digits
2967 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2969 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2971 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972 int depth=0; float score;
2973 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975 pvInfoList[forwardMostMove-1].depth = depth;
2976 pvInfoList[forwardMostMove-1].score = 100*score;
2978 OutputKibitz(suppressKibitz, parse);
2981 if(gameMode == IcsObserving) // restore original ICS messages
2982 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985 SendToPlayer(tmp, strlen(tmp));
2987 next_out = i+1; // [HGM] suppress printing in ICS window
2989 started = STARTED_NONE;
2991 /* Don't match patterns against characters in comment */
2996 if (started == STARTED_CHATTER) {
2997 if (buf[i] != '\n') {
2998 /* Don't match patterns against characters in chatter */
3002 started = STARTED_NONE;
3003 if(suppressKibitz) next_out = i+1;
3006 /* Kludge to deal with rcmd protocol */
3007 if (firstTime && looking_at(buf, &i, "\001*")) {
3008 DisplayFatalError(&buf[1], 0, 1);
3014 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017 if (appData.debugMode)
3018 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022 ics_type = ICS_FICS;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "ics_type %d\n", ics_type);
3028 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029 ics_type = ICS_CHESSNET;
3031 if (appData.debugMode)
3032 fprintf(debugFP, "ics_type %d\n", ics_type);
3037 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038 looking_at(buf, &i, "Logging you in as \"*\"") ||
3039 looking_at(buf, &i, "will be \"*\""))) {
3040 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3044 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047 DisplayIcsInteractionTitle(buf);
3048 have_set_title = TRUE;
3051 /* skip finger notes */
3052 if (started == STARTED_NONE &&
3053 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054 (buf[i] == '1' && buf[i+1] == '0')) &&
3055 buf[i+2] == ':' && buf[i+3] == ' ') {
3056 started = STARTED_CHATTER;
3062 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063 if(appData.seekGraph) {
3064 if(soughtPending && MatchSoughtLine(buf+i)) {
3065 i = strstr(buf+i, "rated") - buf;
3066 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 next_out = leftover_start = i;
3068 started = STARTED_CHATTER;
3069 suppressKibitz = TRUE;
3072 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073 && looking_at(buf, &i, "* ads displayed")) {
3074 soughtPending = FALSE;
3079 if(appData.autoRefresh) {
3080 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081 int s = (ics_type == ICS_ICC); // ICC format differs
3083 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085 looking_at(buf, &i, "*% "); // eat prompt
3086 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 next_out = i; // suppress
3091 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092 char *p = star_match[0];
3094 if(seekGraphUp) RemoveSeekAd(atoi(p));
3095 while(*p && *p++ != ' '); // next
3097 looking_at(buf, &i, "*% "); // eat prompt
3098 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 /* skip formula vars */
3106 if (started == STARTED_NONE &&
3107 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108 started = STARTED_CHATTER;
3113 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114 if (appData.autoKibitz && started == STARTED_NONE &&
3115 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3116 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3121 suppressKibitz = TRUE;
3122 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125 && (gameMode == IcsPlayingWhite)) ||
3126 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3128 started = STARTED_CHATTER; // own kibitz we simply discard
3130 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131 parse_pos = 0; parse[0] = NULLCHAR;
3132 savingComment = TRUE;
3133 suppressKibitz = gameMode != IcsObserving ? 2 :
3134 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3138 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140 && atoi(star_match[0])) {
3141 // suppress the acknowledgements of our own autoKibitz
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145 SendToPlayer(star_match[0], strlen(star_match[0]));
3146 if(looking_at(buf, &i, "*% ")) // eat prompt
3147 suppressKibitz = FALSE;
3151 } // [HGM] kibitz: end of patch
3153 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155 // [HGM] chat: intercept tells by users for which we have an open chat window
3157 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158 looking_at(buf, &i, "* whispers:") ||
3159 looking_at(buf, &i, "* kibitzes:") ||
3160 looking_at(buf, &i, "* shouts:") ||
3161 looking_at(buf, &i, "* c-shouts:") ||
3162 looking_at(buf, &i, "--> * ") ||
3163 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169 chattingPartner = -1;
3171 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174 talker[0] = '['; strcat(talker, "] ");
3175 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176 chattingPartner = p; break;
3179 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(!strcmp("kibitzes", chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 chattingPartner = p; break;
3186 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187 for(p=0; p<MAX_CHAT; p++) {
3188 if(!strcmp("whispers", chatPartner[p])) {
3189 talker[0] = '['; strcat(talker, "] ");
3190 chattingPartner = p; break;
3193 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194 if(buf[i-8] == '-' && buf[i-3] == 't')
3195 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196 if(!strcmp("c-shouts", chatPartner[p])) {
3197 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198 chattingPartner = p; break;
3201 if(chattingPartner < 0)
3202 for(p=0; p<MAX_CHAT; p++) {
3203 if(!strcmp("shouts", chatPartner[p])) {
3204 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207 chattingPartner = p; break;
3211 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213 talker[0] = 0; Colorize(ColorTell, FALSE);
3214 chattingPartner = p; break;
3216 if(chattingPartner<0) i = oldi; else {
3217 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 started = STARTED_COMMENT;
3221 parse_pos = 0; parse[0] = NULLCHAR;
3222 savingComment = 3 + chattingPartner; // counts as TRUE
3223 suppressKibitz = TRUE;
3226 } // [HGM] chat: end of patch
3229 if (appData.zippyTalk || appData.zippyPlay) {
3230 /* [DM] Backup address for color zippy lines */
3232 if (loggedOn == TRUE)
3233 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 } // [DM] 'else { ' deleted
3238 /* Regular tells and says */
3239 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240 looking_at(buf, &i, "* (your partner) tells you: ") ||
3241 looking_at(buf, &i, "* says: ") ||
3242 /* Don't color "message" or "messages" output */
3243 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244 looking_at(buf, &i, "*. * at *:*: ") ||
3245 looking_at(buf, &i, "--* (*:*): ") ||
3246 /* Message notifications (same color as tells) */
3247 looking_at(buf, &i, "* has left a message ") ||
3248 looking_at(buf, &i, "* just sent you a message:\n") ||
3249 /* Whispers and kibitzes */
3250 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251 looking_at(buf, &i, "* kibitzes: ") ||
3253 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255 if (tkind == 1 && strchr(star_match[0], ':')) {
3256 /* Avoid "tells you:" spoofs in channels */
3259 if (star_match[0][0] == NULLCHAR ||
3260 strchr(star_match[0], ' ') ||
3261 (tkind == 3 && strchr(star_match[1], ' '))) {
3262 /* Reject bogus matches */
3265 if (appData.colorize) {
3266 if (oldi > next_out) {
3267 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorTell, FALSE);
3273 curColor = ColorTell;
3276 Colorize(ColorKibitz, FALSE);
3277 curColor = ColorKibitz;
3280 p = strrchr(star_match[1], '(');
3287 Colorize(ColorChannel1, FALSE);
3288 curColor = ColorChannel1;
3290 Colorize(ColorChannel, FALSE);
3291 curColor = ColorChannel;
3295 curColor = ColorNormal;
3299 if (started == STARTED_NONE && appData.autoComment &&
3300 (gameMode == IcsObserving ||
3301 gameMode == IcsPlayingWhite ||
3302 gameMode == IcsPlayingBlack)) {
3303 parse_pos = i - oldi;
3304 memcpy(parse, &buf[oldi], parse_pos);
3305 parse[parse_pos] = NULLCHAR;
3306 started = STARTED_COMMENT;
3307 savingComment = TRUE;
3309 started = STARTED_CHATTER;
3310 savingComment = FALSE;
3317 if (looking_at(buf, &i, "* s-shouts: ") ||
3318 looking_at(buf, &i, "* c-shouts: ")) {
3319 if (appData.colorize) {
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorSShout, FALSE);
3325 curColor = ColorSShout;
3328 started = STARTED_CHATTER;
3332 if (looking_at(buf, &i, "--->")) {
3337 if (looking_at(buf, &i, "* shouts: ") ||
3338 looking_at(buf, &i, "--> ")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorShout, FALSE);
3345 curColor = ColorShout;
3348 started = STARTED_CHATTER;
3352 if (looking_at( buf, &i, "Challenge:")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorChallenge, FALSE);
3359 curColor = ColorChallenge;
3365 if (looking_at(buf, &i, "* offers you") ||
3366 looking_at(buf, &i, "* offers to be") ||
3367 looking_at(buf, &i, "* would like to") ||
3368 looking_at(buf, &i, "* requests to") ||
3369 looking_at(buf, &i, "Your opponent offers") ||
3370 looking_at(buf, &i, "Your opponent requests")) {
3372 if (appData.colorize) {
3373 if (oldi > next_out) {
3374 SendToPlayer(&buf[next_out], oldi - next_out);
3377 Colorize(ColorRequest, FALSE);
3378 curColor = ColorRequest;
3383 if (looking_at(buf, &i, "* (*) seeking")) {
3384 if (appData.colorize) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 Colorize(ColorSeek, FALSE);
3390 curColor = ColorSeek;
3395 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397 if (looking_at(buf, &i, "\\ ")) {
3398 if (prevColor != ColorNormal) {
3399 if (oldi > next_out) {
3400 SendToPlayer(&buf[next_out], oldi - next_out);
3403 Colorize(prevColor, TRUE);
3404 curColor = prevColor;
3406 if (savingComment) {
3407 parse_pos = i - oldi;
3408 memcpy(parse, &buf[oldi], parse_pos);
3409 parse[parse_pos] = NULLCHAR;
3410 started = STARTED_COMMENT;
3411 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412 chattingPartner = savingComment - 3; // kludge to remember the box
3414 started = STARTED_CHATTER;
3419 if (looking_at(buf, &i, "Black Strength :") ||
3420 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421 looking_at(buf, &i, "<10>") ||
3422 looking_at(buf, &i, "#@#")) {
3423 /* Wrong board style */
3425 SendToICS(ics_prefix);
3426 SendToICS("set style 12\n");
3427 SendToICS(ics_prefix);
3428 SendToICS("refresh\n");
3432 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434 have_sent_ICS_logon = 1;
3438 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439 (looking_at(buf, &i, "\n<12> ") ||
3440 looking_at(buf, &i, "<12> "))) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 started = STARTED_BOARD;
3451 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452 looking_at(buf, &i, "<b1> ")) {
3453 if (oldi > next_out) {
3454 SendToPlayer(&buf[next_out], oldi - next_out);
3457 started = STARTED_HOLDINGS;
3462 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464 /* Header for a move list -- first line */
3466 switch (ics_getting_history) {
3470 case BeginningOfGame:
3471 /* User typed "moves" or "oldmoves" while we
3472 were idle. Pretend we asked for these
3473 moves and soak them up so user can step
3474 through them and/or save them.
3477 gameMode = IcsObserving;
3480 ics_getting_history = H_GOT_UNREQ_HEADER;
3482 case EditGame: /*?*/
3483 case EditPosition: /*?*/
3484 /* Should above feature work in these modes too? */
3485 /* For now it doesn't */
3486 ics_getting_history = H_GOT_UNWANTED_HEADER;
3489 ics_getting_history = H_GOT_UNWANTED_HEADER;
3494 /* Is this the right one? */
3495 if (gameInfo.white && gameInfo.black &&
3496 strcmp(gameInfo.white, star_match[0]) == 0 &&
3497 strcmp(gameInfo.black, star_match[2]) == 0) {
3499 ics_getting_history = H_GOT_REQ_HEADER;
3502 case H_GOT_REQ_HEADER:
3503 case H_GOT_UNREQ_HEADER:
3504 case H_GOT_UNWANTED_HEADER:
3505 case H_GETTING_MOVES:
3506 /* Should not happen */
3507 DisplayError(_("Error gathering move list: two headers"), 0);
3508 ics_getting_history = H_FALSE;
3512 /* Save player ratings into gameInfo if needed */
3513 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515 (gameInfo.whiteRating == -1 ||
3516 gameInfo.blackRating == -1)) {
3518 gameInfo.whiteRating = string_to_rating(star_match[1]);
3519 gameInfo.blackRating = string_to_rating(star_match[3]);
3520 if (appData.debugMode)
3521 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522 gameInfo.whiteRating, gameInfo.blackRating);
3527 if (looking_at(buf, &i,
3528 "* * match, initial time: * minute*, increment: * second")) {
3529 /* Header for a move list -- second line */
3530 /* Initial board will follow if this is a wild game */
3531 if (gameInfo.event != NULL) free(gameInfo.event);
3532 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533 gameInfo.event = StrSave(str);
3534 /* [HGM] we switched variant. Translate boards if needed. */
3535 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3539 if (looking_at(buf, &i, "Move ")) {
3540 /* Beginning of a move list */
3541 switch (ics_getting_history) {
3543 /* Normally should not happen */
3544 /* Maybe user hit reset while we were parsing */
3547 /* Happens if we are ignoring a move list that is not
3548 * the one we just requested. Common if the user
3549 * tries to observe two games without turning off
3552 case H_GETTING_MOVES:
3553 /* Should not happen */
3554 DisplayError(_("Error gathering move list: nested"), 0);
3555 ics_getting_history = H_FALSE;
3557 case H_GOT_REQ_HEADER:
3558 ics_getting_history = H_GETTING_MOVES;
3559 started = STARTED_MOVES;
3561 if (oldi > next_out) {
3562 SendToPlayer(&buf[next_out], oldi - next_out);
3565 case H_GOT_UNREQ_HEADER:
3566 ics_getting_history = H_GETTING_MOVES;
3567 started = STARTED_MOVES_NOHIDE;
3570 case H_GOT_UNWANTED_HEADER:
3571 ics_getting_history = H_FALSE;
3577 if (looking_at(buf, &i, "% ") ||
3578 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581 soughtPending = FALSE;
3585 if(suppressKibitz) next_out = i;
3586 savingComment = FALSE;
3590 case STARTED_MOVES_NOHIDE:
3591 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592 parse[parse_pos + i - oldi] = NULLCHAR;
3593 ParseGameHistory(parse);
3595 if (appData.zippyPlay && first.initDone) {
3596 FeedMovesToProgram(&first, forwardMostMove);
3597 if (gameMode == IcsPlayingWhite) {
3598 if (WhiteOnMove(forwardMostMove)) {
3599 if (first.sendTime) {
3600 if (first.useColors) {
3601 SendToProgram("black\n", &first);
3603 SendTimeRemaining(&first, TRUE);
3605 if (first.useColors) {
3606 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609 first.maybeThinking = TRUE;
3611 if (first.usePlayother) {
3612 if (first.sendTime) {
3613 SendTimeRemaining(&first, TRUE);
3615 SendToProgram("playother\n", &first);
3621 } else if (gameMode == IcsPlayingBlack) {
3622 if (!WhiteOnMove(forwardMostMove)) {
3623 if (first.sendTime) {
3624 if (first.useColors) {
3625 SendToProgram("white\n", &first);
3627 SendTimeRemaining(&first, FALSE);
3629 if (first.useColors) {
3630 SendToProgram("black\n", &first);
3632 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633 first.maybeThinking = TRUE;
3635 if (first.usePlayother) {
3636 if (first.sendTime) {
3637 SendTimeRemaining(&first, FALSE);
3639 SendToProgram("playother\n", &first);
3648 if (gameMode == IcsObserving && ics_gamenum == -1) {
3649 /* Moves came from oldmoves or moves command
3650 while we weren't doing anything else.
3652 currentMove = forwardMostMove;
3653 ClearHighlights();/*!!could figure this out*/
3654 flipView = appData.flipView;
3655 DrawPosition(TRUE, boards[currentMove]);
3656 DisplayBothClocks();
3657 snprintf(str, MSG_SIZ, "%s %s %s",
3658 gameInfo.white, _("vs."), gameInfo.black);
3662 /* Moves were history of an active game */
3663 if (gameInfo.resultDetails != NULL) {
3664 free(gameInfo.resultDetails);
3665 gameInfo.resultDetails = NULL;
3668 HistorySet(parseList, backwardMostMove,
3669 forwardMostMove, currentMove-1);
3670 DisplayMove(currentMove - 1);
3671 if (started == STARTED_MOVES) next_out = i;
3672 started = STARTED_NONE;
3673 ics_getting_history = H_FALSE;
3676 case STARTED_OBSERVE:
3677 started = STARTED_NONE;
3678 SendToICS(ics_prefix);
3679 SendToICS("refresh\n");
3685 if(bookHit) { // [HGM] book: simulate book reply
3686 static char bookMove[MSG_SIZ]; // a bit generous?
3688 programStats.nodes = programStats.depth = programStats.time =
3689 programStats.score = programStats.got_only_move = 0;
3690 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693 strcat(bookMove, bookHit);
3694 HandleMachineMove(bookMove, &first);
3699 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700 started == STARTED_HOLDINGS ||
3701 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702 /* Accumulate characters in move list or board */
3703 parse[parse_pos++] = buf[i];
3706 /* Start of game messages. Mostly we detect start of game
3707 when the first board image arrives. On some versions
3708 of the ICS, though, we need to do a "refresh" after starting
3709 to observe in order to get the current board right away. */
3710 if (looking_at(buf, &i, "Adding game * to observation list")) {
3711 started = STARTED_OBSERVE;
3715 /* Handle auto-observe */
3716 if (appData.autoObserve &&
3717 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720 /* Choose the player that was highlighted, if any. */
3721 if (star_match[0][0] == '\033' ||
3722 star_match[1][0] != '\033') {
3723 player = star_match[0];
3725 player = star_match[2];
3727 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728 ics_prefix, StripHighlightAndTitle(player));
3731 /* Save ratings from notify string */
3732 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733 player1Rating = string_to_rating(star_match[1]);
3734 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735 player2Rating = string_to_rating(star_match[3]);
3737 if (appData.debugMode)
3739 "Ratings from 'Game notification:' %s %d, %s %d\n",
3740 player1Name, player1Rating,
3741 player2Name, player2Rating);
3746 /* Deal with automatic examine mode after a game,
3747 and with IcsObserving -> IcsExamining transition */
3748 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749 looking_at(buf, &i, "has made you an examiner of game *")) {
3751 int gamenum = atoi(star_match[0]);
3752 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753 gamenum == ics_gamenum) {
3754 /* We were already playing or observing this game;
3755 no need to refetch history */
3756 gameMode = IcsExamining;
3758 pauseExamForwardMostMove = forwardMostMove;
3759 } else if (currentMove < forwardMostMove) {
3760 ForwardInner(forwardMostMove);
3763 /* I don't think this case really can happen */
3764 SendToICS(ics_prefix);
3765 SendToICS("refresh\n");
3770 /* Error messages */
3771 // if (ics_user_moved) {
3772 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773 if (looking_at(buf, &i, "Illegal move") ||
3774 looking_at(buf, &i, "Not a legal move") ||
3775 looking_at(buf, &i, "Your king is in check") ||
3776 looking_at(buf, &i, "It isn't your turn") ||
3777 looking_at(buf, &i, "It is not your move")) {
3779 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780 currentMove = forwardMostMove-1;
3781 DisplayMove(currentMove - 1); /* before DMError */
3782 DrawPosition(FALSE, boards[currentMove]);
3783 SwitchClocks(forwardMostMove-1); // [HGM] race
3784 DisplayBothClocks();
3786 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3792 if (looking_at(buf, &i, "still have time") ||
3793 looking_at(buf, &i, "not out of time") ||
3794 looking_at(buf, &i, "either player is out of time") ||
3795 looking_at(buf, &i, "has timeseal; checking")) {
3796 /* We must have called his flag a little too soon */
3797 whiteFlag = blackFlag = FALSE;
3801 if (looking_at(buf, &i, "added * seconds to") ||
3802 looking_at(buf, &i, "seconds were added to")) {
3803 /* Update the clocks */
3804 SendToICS(ics_prefix);
3805 SendToICS("refresh\n");
3809 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810 ics_clock_paused = TRUE;
3815 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816 ics_clock_paused = FALSE;
3821 /* Grab player ratings from the Creating: message.
3822 Note we have to check for the special case when
3823 the ICS inserts things like [white] or [black]. */
3824 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827 0 player 1 name (not necessarily white)
3829 2 empty, white, or black (IGNORED)
3830 3 player 2 name (not necessarily black)
3833 The names/ratings are sorted out when the game
3834 actually starts (below).
3836 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837 player1Rating = string_to_rating(star_match[1]);
3838 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839 player2Rating = string_to_rating(star_match[4]);
3841 if (appData.debugMode)
3843 "Ratings from 'Creating:' %s %d, %s %d\n",
3844 player1Name, player1Rating,
3845 player2Name, player2Rating);
3850 /* Improved generic start/end-of-game messages */
3851 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853 /* If tkind == 0: */
3854 /* star_match[0] is the game number */
3855 /* [1] is the white player's name */
3856 /* [2] is the black player's name */
3857 /* For end-of-game: */
3858 /* [3] is the reason for the game end */
3859 /* [4] is a PGN end game-token, preceded by " " */
3860 /* For start-of-game: */
3861 /* [3] begins with "Creating" or "Continuing" */
3862 /* [4] is " *" or empty (don't care). */
3863 int gamenum = atoi(star_match[0]);
3864 char *whitename, *blackname, *why, *endtoken;
3865 ChessMove endtype = EndOfFile;
3868 whitename = star_match[1];
3869 blackname = star_match[2];
3870 why = star_match[3];
3871 endtoken = star_match[4];
3873 whitename = star_match[1];
3874 blackname = star_match[3];
3875 why = star_match[5];
3876 endtoken = star_match[6];
3879 /* Game start messages */
3880 if (strncmp(why, "Creating ", 9) == 0 ||
3881 strncmp(why, "Continuing ", 11) == 0) {
3882 gs_gamenum = gamenum;
3883 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 if (appData.zippyPlay) {
3888 ZippyGameStart(whitename, blackname);
3891 partnerBoardValid = FALSE; // [HGM] bughouse
3895 /* Game end messages */
3896 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897 ics_gamenum != gamenum) {
3900 while (endtoken[0] == ' ') endtoken++;
3901 switch (endtoken[0]) {
3904 endtype = GameUnfinished;
3907 endtype = BlackWins;
3910 if (endtoken[1] == '/')
3911 endtype = GameIsDrawn;
3913 endtype = WhiteWins;
3916 GameEnds(endtype, why, GE_ICS);
3918 if (appData.zippyPlay && first.initDone) {
3919 ZippyGameEnd(endtype, why);
3920 if (first.pr == NoProc) {
3921 /* Start the next process early so that we'll
3922 be ready for the next challenge */
3923 StartChessProgram(&first);
3925 /* Send "new" early, in case this command takes
3926 a long time to finish, so that we'll be ready
3927 for the next challenge. */
3928 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3932 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3936 if (looking_at(buf, &i, "Removing game * from observation") ||
3937 looking_at(buf, &i, "no longer observing game *") ||
3938 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939 if (gameMode == IcsObserving &&
3940 atoi(star_match[0]) == ics_gamenum)
3942 /* icsEngineAnalyze */
3943 if (appData.icsEngineAnalyze) {
3950 ics_user_moved = FALSE;
3955 if (looking_at(buf, &i, "no longer examining game *")) {
3956 if (gameMode == IcsExamining &&
3957 atoi(star_match[0]) == ics_gamenum)
3961 ics_user_moved = FALSE;
3966 /* Advance leftover_start past any newlines we find,
3967 so only partial lines can get reparsed */
3968 if (looking_at(buf, &i, "\n")) {
3969 prevColor = curColor;
3970 if (curColor != ColorNormal) {
3971 if (oldi > next_out) {
3972 SendToPlayer(&buf[next_out], oldi - next_out);
3975 Colorize(ColorNormal, FALSE);
3976 curColor = ColorNormal;
3978 if (started == STARTED_BOARD) {
3979 started = STARTED_NONE;
3980 parse[parse_pos] = NULLCHAR;
3981 ParseBoard12(parse);
3984 /* Send premove here */
3985 if (appData.premove) {
3987 if (currentMove == 0 &&
3988 gameMode == IcsPlayingWhite &&
3989 appData.premoveWhite) {
3990 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991 if (appData.debugMode)
3992 fprintf(debugFP, "Sending premove:\n");
3994 } else if (currentMove == 1 &&
3995 gameMode == IcsPlayingBlack &&
3996 appData.premoveBlack) {
3997 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998 if (appData.debugMode)
3999 fprintf(debugFP, "Sending premove:\n");
4001 } else if (gotPremove) {
4003 ClearPremoveHighlights();
4004 if (appData.debugMode)
4005 fprintf(debugFP, "Sending premove:\n");
4006 UserMoveEvent(premoveFromX, premoveFromY,
4007 premoveToX, premoveToY,
4012 /* Usually suppress following prompt */
4013 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015 if (looking_at(buf, &i, "*% ")) {
4016 savingComment = FALSE;
4021 } else if (started == STARTED_HOLDINGS) {
4023 char new_piece[MSG_SIZ];
4024 started = STARTED_NONE;
4025 parse[parse_pos] = NULLCHAR;
4026 if (appData.debugMode)
4027 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028 parse, currentMove);
4029 if (sscanf(parse, " game %d", &gamenum) == 1) {
4030 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031 if (gameInfo.variant == VariantNormal) {
4032 /* [HGM] We seem to switch variant during a game!
4033 * Presumably no holdings were displayed, so we have
4034 * to move the position two files to the right to
4035 * create room for them!
4037 VariantClass newVariant;
4038 switch(gameInfo.boardWidth) { // base guess on board width
4039 case 9: newVariant = VariantShogi; break;
4040 case 10: newVariant = VariantGreat; break;
4041 default: newVariant = VariantCrazyhouse; break;
4043 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044 /* Get a move list just to see the header, which
4045 will tell us whether this is really bug or zh */
4046 if (ics_getting_history == H_FALSE) {
4047 ics_getting_history = H_REQUESTED;
4048 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4052 new_piece[0] = NULLCHAR;
4053 sscanf(parse, "game %d white [%s black [%s <- %s",
4054 &gamenum, white_holding, black_holding,
4056 white_holding[strlen(white_holding)-1] = NULLCHAR;
4057 black_holding[strlen(black_holding)-1] = NULLCHAR;
4058 /* [HGM] copy holdings to board holdings area */
4059 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 if (appData.zippyPlay && first.initDone) {
4064 ZippyHoldings(white_holding, black_holding,
4068 if (tinyLayout || smallLayout) {
4069 char wh[16], bh[16];
4070 PackHolding(wh, white_holding);
4071 PackHolding(bh, black_holding);
4072 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073 gameInfo.white, gameInfo.black);
4075 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076 gameInfo.white, white_holding, _("vs."),
4077 gameInfo.black, black_holding);
4079 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080 DrawPosition(FALSE, boards[currentMove]);
4082 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083 sscanf(parse, "game %d white [%s black [%s <- %s",
4084 &gamenum, white_holding, black_holding,
4086 white_holding[strlen(white_holding)-1] = NULLCHAR;
4087 black_holding[strlen(black_holding)-1] = NULLCHAR;
4088 /* [HGM] copy holdings to partner-board holdings area */
4089 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4096 /* Suppress following prompt */
4097 if (looking_at(buf, &i, "*% ")) {
4098 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099 savingComment = FALSE;
4107 i++; /* skip unparsed character and loop back */
4110 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 // SendToPlayer(&buf[next_out], i - next_out);
4113 started != STARTED_HOLDINGS && leftover_start > next_out) {
4114 SendToPlayer(&buf[next_out], leftover_start - next_out);
4118 leftover_len = buf_len - leftover_start;
4119 /* if buffer ends with something we couldn't parse,
4120 reparse it after appending the next read */
4122 } else if (count == 0) {
4123 RemoveInputSource(isr);
4124 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126 DisplayFatalError(_("Error reading from ICS"), error, 1);
4131 /* Board style 12 looks like this:
4133 <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
4135 * The "<12> " is stripped before it gets to this routine. The two
4136 * trailing 0's (flip state and clock ticking) are later addition, and
4137 * some chess servers may not have them, or may have only the first.
4138 * Additional trailing fields may be added in the future.
4141 #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"
4143 #define RELATION_OBSERVING_PLAYED 0
4144 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE 1
4146 #define RELATION_PLAYING_NOTMYMOVE -1
4147 #define RELATION_EXAMINING 2
4148 #define RELATION_ISOLATED_BOARD -3
4149 #define RELATION_STARTING_POSITION -4 /* FICS only */
4152 ParseBoard12 (char *string)
4154 GameMode newGameMode;
4155 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158 char to_play, board_chars[200];
4159 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160 char black[32], white[32];
4162 int prevMove = currentMove;
4165 int fromX, fromY, toX, toY;
4167 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168 char *bookHit = NULL; // [HGM] book
4169 Boolean weird = FALSE, reqFlag = FALSE;
4171 fromX = fromY = toX = toY = -1;
4175 if (appData.debugMode)
4176 fprintf(debugFP, _("Parsing board: %s\n"), string);
4178 move_str[0] = NULLCHAR;
4179 elapsed_time[0] = NULLCHAR;
4180 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183 if(string[i] == ' ') { ranks++; files = 0; }
4185 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4188 for(j = 0; j <i; j++) board_chars[j] = string[j];
4189 board_chars[i] = '\0';
4192 n = sscanf(string, PATTERN, &to_play, &double_push,
4193 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194 &gamenum, white, black, &relation, &basetime, &increment,
4195 &white_stren, &black_stren, &white_time, &black_time,
4196 &moveNum, str, elapsed_time, move_str, &ics_flip,
4200 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201 DisplayError(str, 0);
4205 /* Convert the move number to internal form */
4206 moveNum = (moveNum - 1) * 2;
4207 if (to_play == 'B') moveNum++;
4208 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4215 case RELATION_OBSERVING_PLAYED:
4216 case RELATION_OBSERVING_STATIC:
4217 if (gamenum == -1) {
4218 /* Old ICC buglet */
4219 relation = RELATION_OBSERVING_STATIC;
4221 newGameMode = IcsObserving;
4223 case RELATION_PLAYING_MYMOVE:
4224 case RELATION_PLAYING_NOTMYMOVE:
4226 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227 IcsPlayingWhite : IcsPlayingBlack;
4228 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230 case RELATION_EXAMINING:
4231 newGameMode = IcsExamining;
4233 case RELATION_ISOLATED_BOARD:
4235 /* Just display this board. If user was doing something else,
4236 we will forget about it until the next board comes. */
4237 newGameMode = IcsIdle;
4239 case RELATION_STARTING_POSITION:
4240 newGameMode = gameMode;
4244 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248 for (k = 0; k < ranks; k++) {
4249 for (j = 0; j < files; j++)
4250 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251 if(gameInfo.holdingsWidth > 1) {
4252 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4256 CopyBoard(partnerBoard, board);
4257 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261 if(toSqr = strchr(str, '-')) {
4262 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4269 DisplayWhiteClock(white_time, to_play == 'W');
4270 DisplayBlackClock(black_time, to_play != 'W');
4271 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4272 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4273 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4274 DisplayMessage(partnerStatus, "");
4275 partnerBoardValid = TRUE;
4279 /* Modify behavior for initial board display on move listing
4282 switch (ics_getting_history) {
4286 case H_GOT_REQ_HEADER:
4287 case H_GOT_UNREQ_HEADER:
4288 /* This is the initial position of the current game */
4289 gamenum = ics_gamenum;
4290 moveNum = 0; /* old ICS bug workaround */
4291 if (to_play == 'B') {
4292 startedFromSetupPosition = TRUE;
4293 blackPlaysFirst = TRUE;
4295 if (forwardMostMove == 0) forwardMostMove = 1;
4296 if (backwardMostMove == 0) backwardMostMove = 1;
4297 if (currentMove == 0) currentMove = 1;
4299 newGameMode = gameMode;
4300 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4302 case H_GOT_UNWANTED_HEADER:
4303 /* This is an initial board that we don't want */
4305 case H_GETTING_MOVES:
4306 /* Should not happen */
4307 DisplayError(_("Error gathering move list: extra board"), 0);
4308 ics_getting_history = H_FALSE;
4312 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4313 weird && (int)gameInfo.variant < (int)VariantShogi) {
4314 /* [HGM] We seem to have switched variant unexpectedly
4315 * Try to guess new variant from board size
4317 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4318 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4319 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4320 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4321 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4322 if(!weird) newVariant = VariantNormal;
4323 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4324 /* Get a move list just to see the header, which
4325 will tell us whether this is really bug or zh */
4326 if (ics_getting_history == H_FALSE) {
4327 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4328 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4333 /* Take action if this is the first board of a new game, or of a
4334 different game than is currently being displayed. */
4335 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4336 relation == RELATION_ISOLATED_BOARD) {
4338 /* Forget the old game and get the history (if any) of the new one */
4339 if (gameMode != BeginningOfGame) {
4343 if (appData.autoRaiseBoard) BoardToTop();
4345 if (gamenum == -1) {
4346 newGameMode = IcsIdle;
4347 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4348 appData.getMoveList && !reqFlag) {
4349 /* Need to get game history */
4350 ics_getting_history = H_REQUESTED;
4351 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4355 /* Initially flip the board to have black on the bottom if playing
4356 black or if the ICS flip flag is set, but let the user change
4357 it with the Flip View button. */
4358 flipView = appData.autoFlipView ?
4359 (newGameMode == IcsPlayingBlack) || ics_flip :
4362 /* Done with values from previous mode; copy in new ones */
4363 gameMode = newGameMode;
4365 ics_gamenum = gamenum;
4366 if (gamenum == gs_gamenum) {
4367 int klen = strlen(gs_kind);
4368 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4369 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4370 gameInfo.event = StrSave(str);
4372 gameInfo.event = StrSave("ICS game");
4374 gameInfo.site = StrSave(appData.icsHost);
4375 gameInfo.date = PGNDate();
4376 gameInfo.round = StrSave("-");
4377 gameInfo.white = StrSave(white);
4378 gameInfo.black = StrSave(black);
4379 timeControl = basetime * 60 * 1000;
4381 timeIncrement = increment * 1000;
4382 movesPerSession = 0;
4383 gameInfo.timeControl = TimeControlTagValue();
4384 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4385 if (appData.debugMode) {
4386 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4387 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4388 setbuf(debugFP, NULL);
4391 gameInfo.outOfBook = NULL;
4393 /* Do we have the ratings? */
4394 if (strcmp(player1Name, white) == 0 &&
4395 strcmp(player2Name, black) == 0) {
4396 if (appData.debugMode)
4397 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398 player1Rating, player2Rating);
4399 gameInfo.whiteRating = player1Rating;
4400 gameInfo.blackRating = player2Rating;
4401 } else if (strcmp(player2Name, white) == 0 &&
4402 strcmp(player1Name, black) == 0) {
4403 if (appData.debugMode)
4404 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4405 player2Rating, player1Rating);
4406 gameInfo.whiteRating = player2Rating;
4407 gameInfo.blackRating = player1Rating;
4409 player1Name[0] = player2Name[0] = NULLCHAR;
4411 /* Silence shouts if requested */
4412 if (appData.quietPlay &&
4413 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4414 SendToICS(ics_prefix);
4415 SendToICS("set shout 0\n");
4419 /* Deal with midgame name changes */
4421 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4422 if (gameInfo.white) free(gameInfo.white);
4423 gameInfo.white = StrSave(white);
4425 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4426 if (gameInfo.black) free(gameInfo.black);
4427 gameInfo.black = StrSave(black);
4431 /* Throw away game result if anything actually changes in examine mode */
4432 if (gameMode == IcsExamining && !newGame) {
4433 gameInfo.result = GameUnfinished;
4434 if (gameInfo.resultDetails != NULL) {
4435 free(gameInfo.resultDetails);
4436 gameInfo.resultDetails = NULL;
4440 /* In pausing && IcsExamining mode, we ignore boards coming
4441 in if they are in a different variation than we are. */
4442 if (pauseExamInvalid) return;
4443 if (pausing && gameMode == IcsExamining) {
4444 if (moveNum <= pauseExamForwardMostMove) {
4445 pauseExamInvalid = TRUE;
4446 forwardMostMove = pauseExamForwardMostMove;
4451 if (appData.debugMode) {
4452 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4454 /* Parse the board */
4455 for (k = 0; k < ranks; k++) {
4456 for (j = 0; j < files; j++)
4457 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4458 if(gameInfo.holdingsWidth > 1) {
4459 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4460 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4463 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4464 board[5][BOARD_RGHT+1] = WhiteAngel;
4465 board[6][BOARD_RGHT+1] = WhiteMarshall;
4466 board[1][0] = BlackMarshall;
4467 board[2][0] = BlackAngel;
4468 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4470 CopyBoard(boards[moveNum], board);
4471 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4473 startedFromSetupPosition =
4474 !CompareBoards(board, initialPosition);
4475 if(startedFromSetupPosition)
4476 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4479 /* [HGM] Set castling rights. Take the outermost Rooks,
4480 to make it also work for FRC opening positions. Note that board12
4481 is really defective for later FRC positions, as it has no way to
4482 indicate which Rook can castle if they are on the same side of King.
4483 For the initial position we grant rights to the outermost Rooks,
4484 and remember thos rights, and we then copy them on positions
4485 later in an FRC game. This means WB might not recognize castlings with
4486 Rooks that have moved back to their original position as illegal,
4487 but in ICS mode that is not its job anyway.
4489 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4490 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4492 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4493 if(board[0][i] == WhiteRook) j = i;
4494 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4496 if(board[0][i] == WhiteRook) j = i;
4497 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4505 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4506 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4507 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4508 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4509 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4510 if(board[BOARD_HEIGHT-1][k] == bKing)
4511 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4512 if(gameInfo.variant == VariantTwoKings) {
4513 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4514 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4515 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4518 r = boards[moveNum][CASTLING][0] = initialRights[0];
4519 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4520 r = boards[moveNum][CASTLING][1] = initialRights[1];
4521 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4522 r = boards[moveNum][CASTLING][3] = initialRights[3];
4523 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4524 r = boards[moveNum][CASTLING][4] = initialRights[4];
4525 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4526 /* wildcastle kludge: always assume King has rights */
4527 r = boards[moveNum][CASTLING][2] = initialRights[2];
4528 r = boards[moveNum][CASTLING][5] = initialRights[5];
4530 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4531 boards[moveNum][EP_STATUS] = EP_NONE;
4532 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4533 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4534 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4537 if (ics_getting_history == H_GOT_REQ_HEADER ||
4538 ics_getting_history == H_GOT_UNREQ_HEADER) {
4539 /* This was an initial position from a move list, not
4540 the current position */
4544 /* Update currentMove and known move number limits */
4545 newMove = newGame || moveNum > forwardMostMove;
4548 forwardMostMove = backwardMostMove = currentMove = moveNum;
4549 if (gameMode == IcsExamining && moveNum == 0) {
4550 /* Workaround for ICS limitation: we are not told the wild
4551 type when starting to examine a game. But if we ask for
4552 the move list, the move list header will tell us */
4553 ics_getting_history = H_REQUESTED;
4554 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4557 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4558 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4560 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4561 /* [HGM] applied this also to an engine that is silently watching */
4562 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4563 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4564 gameInfo.variant == currentlyInitializedVariant) {
4565 takeback = forwardMostMove - moveNum;
4566 for (i = 0; i < takeback; i++) {
4567 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4568 SendToProgram("undo\n", &first);
4573 forwardMostMove = moveNum;
4574 if (!pausing || currentMove > forwardMostMove)
4575 currentMove = forwardMostMove;
4577 /* New part of history that is not contiguous with old part */
4578 if (pausing && gameMode == IcsExamining) {
4579 pauseExamInvalid = TRUE;
4580 forwardMostMove = pauseExamForwardMostMove;
4583 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4585 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4586 // [HGM] when we will receive the move list we now request, it will be
4587 // fed to the engine from the first move on. So if the engine is not
4588 // in the initial position now, bring it there.
4589 InitChessProgram(&first, 0);
4592 ics_getting_history = H_REQUESTED;
4593 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4596 forwardMostMove = backwardMostMove = currentMove = moveNum;
4599 /* Update the clocks */
4600 if (strchr(elapsed_time, '.')) {
4602 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4603 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4605 /* Time is in seconds */
4606 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4607 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4612 if (appData.zippyPlay && newGame &&
4613 gameMode != IcsObserving && gameMode != IcsIdle &&
4614 gameMode != IcsExamining)
4615 ZippyFirstBoard(moveNum, basetime, increment);
4618 /* Put the move on the move list, first converting
4619 to canonical algebraic form. */
4621 if (appData.debugMode) {
4622 if (appData.debugMode) { int f = forwardMostMove;
4623 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4624 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4625 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4627 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4628 fprintf(debugFP, "moveNum = %d\n", moveNum);
4629 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4630 setbuf(debugFP, NULL);
4632 if (moveNum <= backwardMostMove) {
4633 /* We don't know what the board looked like before
4635 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636 strcat(parseList[moveNum - 1], " ");
4637 strcat(parseList[moveNum - 1], elapsed_time);
4638 moveList[moveNum - 1][0] = NULLCHAR;
4639 } else if (strcmp(move_str, "none") == 0) {
4640 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4641 /* Again, we don't know what the board looked like;
4642 this is really the start of the game. */
4643 parseList[moveNum - 1][0] = NULLCHAR;
4644 moveList[moveNum - 1][0] = NULLCHAR;
4645 backwardMostMove = moveNum;
4646 startedFromSetupPosition = TRUE;
4647 fromX = fromY = toX = toY = -1;
4649 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4650 // So we parse the long-algebraic move string in stead of the SAN move
4651 int valid; char buf[MSG_SIZ], *prom;
4653 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4654 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4655 // str looks something like "Q/a1-a2"; kill the slash
4657 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4658 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4659 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4660 strcat(buf, prom); // long move lacks promo specification!
4661 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4662 if(appData.debugMode)
4663 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4664 safeStrCpy(move_str, buf, MSG_SIZ);
4666 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4667 &fromX, &fromY, &toX, &toY, &promoChar)
4668 || ParseOneMove(buf, moveNum - 1, &moveType,
4669 &fromX, &fromY, &toX, &toY, &promoChar);
4670 // end of long SAN patch
4672 (void) CoordsToAlgebraic(boards[moveNum - 1],
4673 PosFlags(moveNum - 1),
4674 fromY, fromX, toY, toX, promoChar,
4675 parseList[moveNum-1]);
4676 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4682 if(gameInfo.variant != VariantShogi)
4683 strcat(parseList[moveNum - 1], "+");
4686 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4687 strcat(parseList[moveNum - 1], "#");
4690 strcat(parseList[moveNum - 1], " ");
4691 strcat(parseList[moveNum - 1], elapsed_time);
4692 /* currentMoveString is set as a side-effect of ParseOneMove */
4693 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4694 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4695 strcat(moveList[moveNum - 1], "\n");
4697 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4698 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4699 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4700 ChessSquare old, new = boards[moveNum][k][j];
4701 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4702 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4703 if(old == new) continue;
4704 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4705 else if(new == WhiteWazir || new == BlackWazir) {
4706 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4707 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4708 else boards[moveNum][k][j] = old; // preserve type of Gold
4709 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4710 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4713 /* Move from ICS was illegal!? Punt. */
4714 if (appData.debugMode) {
4715 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4716 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
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 fromX = fromY = toX = toY = -1;
4725 if (appData.debugMode) {
4726 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4727 setbuf(debugFP, NULL);
4731 /* Send move to chess program (BEFORE animating it). */
4732 if (appData.zippyPlay && !newGame && newMove &&
4733 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4735 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4736 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4737 if (moveList[moveNum - 1][0] == NULLCHAR) {
4738 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4740 DisplayError(str, 0);
4742 if (first.sendTime) {
4743 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4745 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4746 if (firstMove && !bookHit) {
4748 if (first.useColors) {
4749 SendToProgram(gameMode == IcsPlayingWhite ?
4751 "black\ngo\n", &first);
4753 SendToProgram("go\n", &first);
4755 first.maybeThinking = TRUE;
4758 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4759 if (moveList[moveNum - 1][0] == NULLCHAR) {
4760 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4761 DisplayError(str, 0);
4763 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4764 SendMoveToProgram(moveNum - 1, &first);
4771 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4772 /* If move comes from a remote source, animate it. If it
4773 isn't remote, it will have already been animated. */
4774 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4775 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4777 if (!pausing && appData.highlightLastMove) {
4778 SetHighlights(fromX, fromY, toX, toY);
4782 /* Start the clocks */
4783 whiteFlag = blackFlag = FALSE;
4784 appData.clockMode = !(basetime == 0 && increment == 0);
4786 ics_clock_paused = TRUE;
4788 } else if (ticking == 1) {
4789 ics_clock_paused = FALSE;
4791 if (gameMode == IcsIdle ||
4792 relation == RELATION_OBSERVING_STATIC ||
4793 relation == RELATION_EXAMINING ||
4795 DisplayBothClocks();
4799 /* Display opponents and material strengths */
4800 if (gameInfo.variant != VariantBughouse &&
4801 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4802 if (tinyLayout || smallLayout) {
4803 if(gameInfo.variant == VariantNormal)
4804 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4805 gameInfo.white, white_stren, gameInfo.black, black_stren,
4806 basetime, increment);
4808 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4809 gameInfo.white, white_stren, gameInfo.black, black_stren,
4810 basetime, increment, (int) gameInfo.variant);
4812 if(gameInfo.variant == VariantNormal)
4813 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4814 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4815 basetime, increment);
4817 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4818 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4819 basetime, increment, VariantName(gameInfo.variant));
4822 if (appData.debugMode) {
4823 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4828 /* Display the board */
4829 if (!pausing && !appData.noGUI) {
4831 if (appData.premove)
4833 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4834 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4835 ClearPremoveHighlights();
4837 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4838 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4839 DrawPosition(j, boards[currentMove]);
4841 DisplayMove(moveNum - 1);
4842 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4843 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4844 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4845 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4849 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4851 if(bookHit) { // [HGM] book: simulate book reply
4852 static char bookMove[MSG_SIZ]; // a bit generous?
4854 programStats.nodes = programStats.depth = programStats.time =
4855 programStats.score = programStats.got_only_move = 0;
4856 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4858 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4859 strcat(bookMove, bookHit);
4860 HandleMachineMove(bookMove, &first);
4869 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4870 ics_getting_history = H_REQUESTED;
4871 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4877 AnalysisPeriodicEvent (int force)
4879 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4880 && !force) || !appData.periodicUpdates)
4883 /* Send . command to Crafty to collect stats */
4884 SendToProgram(".\n", &first);
4886 /* Don't send another until we get a response (this makes
4887 us stop sending to old Crafty's which don't understand
4888 the "." command (sending illegal cmds resets node count & time,
4889 which looks bad)) */
4890 programStats.ok_to_send = 0;
4894 ics_update_width (int new_width)
4896 ics_printf("set width %d\n", new_width);
4900 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4904 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4905 // null move in variant where engine does not understand it (for analysis purposes)
4906 SendBoard(cps, moveNum + 1); // send position after move in stead.
4909 if (cps->useUsermove) {
4910 SendToProgram("usermove ", cps);
4914 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4915 int len = space - parseList[moveNum];
4916 memcpy(buf, parseList[moveNum], len);
4918 buf[len] = NULLCHAR;
4920 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4922 SendToProgram(buf, cps);
4924 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4925 AlphaRank(moveList[moveNum], 4);
4926 SendToProgram(moveList[moveNum], cps);
4927 AlphaRank(moveList[moveNum], 4); // and back
4929 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4930 * the engine. It would be nice to have a better way to identify castle
4932 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4933 && cps->useOOCastle) {
4934 int fromX = moveList[moveNum][0] - AAA;
4935 int fromY = moveList[moveNum][1] - ONE;
4936 int toX = moveList[moveNum][2] - AAA;
4937 int toY = moveList[moveNum][3] - ONE;
4938 if((boards[moveNum][fromY][fromX] == WhiteKing
4939 && boards[moveNum][toY][toX] == WhiteRook)
4940 || (boards[moveNum][fromY][fromX] == BlackKing
4941 && boards[moveNum][toY][toX] == BlackRook)) {
4942 if(toX > fromX) SendToProgram("O-O\n", cps);
4943 else SendToProgram("O-O-O\n", cps);
4945 else SendToProgram(moveList[moveNum], cps);
4947 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4948 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4949 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4950 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4951 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4953 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4954 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955 SendToProgram(buf, cps);
4957 else SendToProgram(moveList[moveNum], cps);
4958 /* End of additions by Tord */
4961 /* [HGM] setting up the opening has brought engine in force mode! */
4962 /* Send 'go' if we are in a mode where machine should play. */
4963 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4964 (gameMode == TwoMachinesPlay ||
4966 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4968 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4969 SendToProgram("go\n", cps);
4970 if (appData.debugMode) {
4971 fprintf(debugFP, "(extra)\n");
4974 setboardSpoiledMachineBlack = 0;
4978 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4980 char user_move[MSG_SIZ];
4983 if(gameInfo.variant == VariantSChess && promoChar) {
4984 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4985 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4986 } else suffix[0] = NULLCHAR;
4990 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4991 (int)moveType, fromX, fromY, toX, toY);
4992 DisplayError(user_move + strlen("say "), 0);
4994 case WhiteKingSideCastle:
4995 case BlackKingSideCastle:
4996 case WhiteQueenSideCastleWild:
4997 case BlackQueenSideCastleWild:
4999 case WhiteHSideCastleFR:
5000 case BlackHSideCastleFR:
5002 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5004 case WhiteQueenSideCastle:
5005 case BlackQueenSideCastle:
5006 case WhiteKingSideCastleWild:
5007 case BlackKingSideCastleWild:
5009 case WhiteASideCastleFR:
5010 case BlackASideCastleFR:
5012 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5014 case WhiteNonPromotion:
5015 case BlackNonPromotion:
5016 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5018 case WhitePromotion:
5019 case BlackPromotion:
5020 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5021 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5022 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5023 PieceToChar(WhiteFerz));
5024 else if(gameInfo.variant == VariantGreat)
5025 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5026 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5027 PieceToChar(WhiteMan));
5029 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5030 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5036 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5037 ToUpper(PieceToChar((ChessSquare) fromX)),
5038 AAA + toX, ONE + toY);
5040 case IllegalMove: /* could be a variant we don't quite understand */
5041 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5043 case WhiteCapturesEnPassant:
5044 case BlackCapturesEnPassant:
5045 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5046 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5049 SendToICS(user_move);
5050 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5051 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5056 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5057 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5058 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5059 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5060 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5063 if(gameMode != IcsExamining) { // is this ever not the case?
5064 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5066 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5067 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5068 } else { // on FICS we must first go to general examine mode
5069 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5071 if(gameInfo.variant != VariantNormal) {
5072 // try figure out wild number, as xboard names are not always valid on ICS
5073 for(i=1; i<=36; i++) {
5074 snprintf(buf, MSG_SIZ, "wild/%d", i);
5075 if(StringToVariant(buf) == gameInfo.variant) break;
5077 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5078 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5079 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5080 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5081 SendToICS(ics_prefix);
5083 if(startedFromSetupPosition || backwardMostMove != 0) {
5084 fen = PositionToFEN(backwardMostMove, NULL);
5085 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5086 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5088 } else { // FICS: everything has to set by separate bsetup commands
5089 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5090 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5092 if(!WhiteOnMove(backwardMostMove)) {
5093 SendToICS("bsetup tomove black\n");
5095 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5096 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5098 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5099 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5101 i = boards[backwardMostMove][EP_STATUS];
5102 if(i >= 0) { // set e.p.
5103 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5109 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5110 SendToICS("bsetup done\n"); // switch to normal examining.
5112 for(i = backwardMostMove; i<last; i++) {
5114 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5117 SendToICS(ics_prefix);
5118 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5122 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5124 if (rf == DROP_RANK) {
5125 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5126 sprintf(move, "%c@%c%c\n",
5127 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5129 if (promoChar == 'x' || promoChar == NULLCHAR) {
5130 sprintf(move, "%c%c%c%c\n",
5131 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5133 sprintf(move, "%c%c%c%c%c\n",
5134 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5140 ProcessICSInitScript (FILE *f)
5144 while (fgets(buf, MSG_SIZ, f)) {
5145 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5152 static int lastX, lastY, selectFlag, dragging;
5157 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5158 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5159 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5160 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5161 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5162 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5165 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5166 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5167 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5168 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5169 if(!step) step = -1;
5170 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5171 appData.testLegality && (promoSweep == king ||
5172 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5173 ChangeDragPiece(promoSweep);
5177 PromoScroll (int x, int y)
5181 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5182 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5183 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5184 if(!step) return FALSE;
5185 lastX = x; lastY = y;
5186 if((promoSweep < BlackPawn) == flipView) step = -step;
5187 if(step > 0) selectFlag = 1;
5188 if(!selectFlag) Sweep(step);
5193 NextPiece (int step)
5195 ChessSquare piece = boards[currentMove][toY][toX];
5198 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5199 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5200 if(!step) step = -1;
5201 } while(PieceToChar(pieceSweep) == '.');
5202 boards[currentMove][toY][toX] = pieceSweep;
5203 DrawPosition(FALSE, boards[currentMove]);
5204 boards[currentMove][toY][toX] = piece;
5206 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5208 AlphaRank (char *move, int n)
5210 // char *p = move, c; int x, y;
5212 if (appData.debugMode) {
5213 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5217 move[2]>='0' && move[2]<='9' &&
5218 move[3]>='a' && move[3]<='x' ) {
5220 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5221 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5223 if(move[0]>='0' && move[0]<='9' &&
5224 move[1]>='a' && move[1]<='x' &&
5225 move[2]>='0' && move[2]<='9' &&
5226 move[3]>='a' && move[3]<='x' ) {
5227 /* input move, Shogi -> normal */
5228 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5229 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5230 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5231 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5234 move[3]>='0' && move[3]<='9' &&
5235 move[2]>='a' && move[2]<='x' ) {
5237 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5241 move[0]>='a' && move[0]<='x' &&
5242 move[3]>='0' && move[3]<='9' &&
5243 move[2]>='a' && move[2]<='x' ) {
5244 /* output move, normal -> Shogi */
5245 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5246 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5247 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5248 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5249 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5251 if (appData.debugMode) {
5252 fprintf(debugFP, " out = '%s'\n", move);
5256 char yy_textstr[8000];
5258 /* Parser for moves from gnuchess, ICS, or user typein box */
5260 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5262 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5264 switch (*moveType) {
5265 case WhitePromotion:
5266 case BlackPromotion:
5267 case WhiteNonPromotion:
5268 case BlackNonPromotion:
5270 case WhiteCapturesEnPassant:
5271 case BlackCapturesEnPassant:
5272 case WhiteKingSideCastle:
5273 case WhiteQueenSideCastle:
5274 case BlackKingSideCastle:
5275 case BlackQueenSideCastle:
5276 case WhiteKingSideCastleWild:
5277 case WhiteQueenSideCastleWild:
5278 case BlackKingSideCastleWild:
5279 case BlackQueenSideCastleWild:
5280 /* Code added by Tord: */
5281 case WhiteHSideCastleFR:
5282 case WhiteASideCastleFR:
5283 case BlackHSideCastleFR:
5284 case BlackASideCastleFR:
5285 /* End of code added by Tord */
5286 case IllegalMove: /* bug or odd chess variant */
5287 *fromX = currentMoveString[0] - AAA;
5288 *fromY = currentMoveString[1] - ONE;
5289 *toX = currentMoveString[2] - AAA;
5290 *toY = currentMoveString[3] - ONE;
5291 *promoChar = currentMoveString[4];
5292 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5293 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5294 if (appData.debugMode) {
5295 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5297 *fromX = *fromY = *toX = *toY = 0;
5300 if (appData.testLegality) {
5301 return (*moveType != IllegalMove);
5303 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5304 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5309 *fromX = *moveType == WhiteDrop ?
5310 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5311 (int) CharToPiece(ToLower(currentMoveString[0]));
5313 *toX = currentMoveString[2] - AAA;
5314 *toY = currentMoveString[3] - ONE;
5315 *promoChar = NULLCHAR;
5319 case ImpossibleMove:
5329 if (appData.debugMode) {
5330 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5333 *fromX = *fromY = *toX = *toY = 0;
5334 *promoChar = NULLCHAR;
5339 Boolean pushed = FALSE;
5340 char *lastParseAttempt;
5343 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5344 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5345 int fromX, fromY, toX, toY; char promoChar;
5350 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5351 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5354 endPV = forwardMostMove;
5356 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5357 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5358 lastParseAttempt = pv;
5359 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5360 if(!valid && nr == 0 &&
5361 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5362 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5363 // Hande case where played move is different from leading PV move
5364 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5365 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5366 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5367 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5368 endPV += 2; // if position different, keep this
5369 moveList[endPV-1][0] = fromX + AAA;
5370 moveList[endPV-1][1] = fromY + ONE;
5371 moveList[endPV-1][2] = toX + AAA;
5372 moveList[endPV-1][3] = toY + ONE;
5373 parseList[endPV-1][0] = NULLCHAR;
5374 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5377 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5378 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5379 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5380 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5381 valid++; // allow comments in PV
5385 if(endPV+1 > framePtr) break; // no space, truncate
5388 CopyBoard(boards[endPV], boards[endPV-1]);
5389 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5390 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5391 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5392 CoordsToAlgebraic(boards[endPV - 1],
5393 PosFlags(endPV - 1),
5394 fromY, fromX, toY, toX, promoChar,
5395 parseList[endPV - 1]);
5397 if(atEnd == 2) return; // used hidden, for PV conversion
5398 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5399 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5400 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5401 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5402 DrawPosition(TRUE, boards[currentMove]);
5406 MultiPV (ChessProgramState *cps)
5407 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5409 for(i=0; i<cps->nrOptions; i++)
5410 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5416 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5418 int startPV, multi, lineStart, origIndex = index;
5419 char *p, buf2[MSG_SIZ];
5421 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5422 lastX = x; lastY = y;
5423 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5424 lineStart = startPV = index;
5425 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5426 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5428 do{ while(buf[index] && buf[index] != '\n') index++;
5429 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5431 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5432 int n = first.option[multi].value;
5433 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5434 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5435 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5436 first.option[multi].value = n;
5439 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5440 ExcludeClick(origIndex - lineStart);
5443 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5444 *start = startPV; *end = index-1;
5451 static char buf[10*MSG_SIZ];
5452 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5454 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5455 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5456 for(i = forwardMostMove; i<endPV; i++){
5457 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5458 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5461 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5462 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5468 LoadPV (int x, int y)
5469 { // called on right mouse click to load PV
5470 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5471 lastX = x; lastY = y;
5472 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5479 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5480 if(endPV < 0) return;
5481 if(appData.autoCopyPV) CopyFENToClipboard();
5483 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5484 Boolean saveAnimate = appData.animate;
5486 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5487 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5488 } else storedGames--; // abandon shelved tail of original game
5491 forwardMostMove = currentMove;
5492 currentMove = oldFMM;
5493 appData.animate = FALSE;
5494 ToNrEvent(forwardMostMove);
5495 appData.animate = saveAnimate;
5497 currentMove = forwardMostMove;
5498 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5499 ClearPremoveHighlights();
5500 DrawPosition(TRUE, boards[currentMove]);
5504 MovePV (int x, int y, int h)
5505 { // step through PV based on mouse coordinates (called on mouse move)
5506 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5508 // we must somehow check if right button is still down (might be released off board!)
5509 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5510 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5511 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5513 lastX = x; lastY = y;
5515 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5516 if(endPV < 0) return;
5517 if(y < margin) step = 1; else
5518 if(y > h - margin) step = -1;
5519 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5520 currentMove += step;
5521 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5522 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5523 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5524 DrawPosition(FALSE, boards[currentMove]);
5528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5529 // All positions will have equal probability, but the current method will not provide a unique
5530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5536 int piecesLeft[(int)BlackPawn];
5537 int seed, nrOfShuffles;
5540 GetPositionNumber ()
5541 { // sets global variable seed
5544 seed = appData.defaultFrcPosition;
5545 if(seed < 0) { // randomize based on time for negative FRC position numbers
5546 for(i=0; i<50; i++) seed += random();
5547 seed = random() ^ random() >> 8 ^ random() << 8;
5548 if(seed<0) seed = -seed;
5553 put (Board board, int pieceType, int rank, int n, int shade)
5554 // put the piece on the (n-1)-th empty squares of the given shade
5558 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5559 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5560 board[rank][i] = (ChessSquare) pieceType;
5561 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5563 piecesLeft[pieceType]--;
5572 AddOnePiece (Board board, int pieceType, int rank, int shade)
5573 // calculate where the next piece goes, (any empty square), and put it there
5577 i = seed % squaresLeft[shade];
5578 nrOfShuffles *= squaresLeft[shade];
5579 seed /= squaresLeft[shade];
5580 put(board, pieceType, rank, i, shade);
5584 AddTwoPieces (Board board, int pieceType, int rank)
5585 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5587 int i, n=squaresLeft[ANY], j=n-1, k;
5589 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5590 i = seed % k; // pick one
5593 while(i >= j) i -= j--;
5594 j = n - 1 - j; i += j;
5595 put(board, pieceType, rank, j, ANY);
5596 put(board, pieceType, rank, i, ANY);
5600 SetUpShuffle (Board board, int number)
5604 GetPositionNumber(); nrOfShuffles = 1;
5606 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5607 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5608 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5610 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5612 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5613 p = (int) board[0][i];
5614 if(p < (int) BlackPawn) piecesLeft[p] ++;
5615 board[0][i] = EmptySquare;
5618 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5619 // shuffles restricted to allow normal castling put KRR first
5620 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5621 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5622 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5623 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5624 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5625 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5626 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5627 put(board, WhiteRook, 0, 0, ANY);
5628 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5631 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5632 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5633 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5634 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5635 while(piecesLeft[p] >= 2) {
5636 AddOnePiece(board, p, 0, LITE);
5637 AddOnePiece(board, p, 0, DARK);
5639 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5642 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5643 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5644 // but we leave King and Rooks for last, to possibly obey FRC restriction
5645 if(p == (int)WhiteRook) continue;
5646 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5647 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5650 // now everything is placed, except perhaps King (Unicorn) and Rooks
5652 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5653 // Last King gets castling rights
5654 while(piecesLeft[(int)WhiteUnicorn]) {
5655 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5659 while(piecesLeft[(int)WhiteKing]) {
5660 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5661 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5666 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5667 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5670 // Only Rooks can be left; simply place them all
5671 while(piecesLeft[(int)WhiteRook]) {
5672 i = put(board, WhiteRook, 0, 0, ANY);
5673 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5676 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5678 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5681 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5682 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5685 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5689 SetCharTable (char *table, const char * map)
5690 /* [HGM] moved here from winboard.c because of its general usefulness */
5691 /* Basically a safe strcpy that uses the last character as King */
5693 int result = FALSE; int NrPieces;
5695 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5696 && NrPieces >= 12 && !(NrPieces&1)) {
5697 int i; /* [HGM] Accept even length from 12 to 34 */
5699 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5700 for( i=0; i<NrPieces/2-1; i++ ) {
5702 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5704 table[(int) WhiteKing] = map[NrPieces/2-1];
5705 table[(int) BlackKing] = map[NrPieces-1];
5714 Prelude (Board board)
5715 { // [HGM] superchess: random selection of exo-pieces
5716 int i, j, k; ChessSquare p;
5717 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5719 GetPositionNumber(); // use FRC position number
5721 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5722 SetCharTable(pieceToChar, appData.pieceToCharTable);
5723 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5724 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5727 j = seed%4; seed /= 4;
5728 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5729 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5730 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5731 j = seed%3 + (seed%3 >= j); seed /= 3;
5732 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5733 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5734 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5735 j = seed%3; seed /= 3;
5736 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5737 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5738 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5739 j = seed%2 + (seed%2 >= j); seed /= 2;
5740 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5741 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5742 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5743 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5744 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5745 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5746 put(board, exoPieces[0], 0, 0, ANY);
5747 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5751 InitPosition (int redraw)
5753 ChessSquare (* pieces)[BOARD_FILES];
5754 int i, j, pawnRow, overrule,
5755 oldx = gameInfo.boardWidth,
5756 oldy = gameInfo.boardHeight,
5757 oldh = gameInfo.holdingsWidth;
5760 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5762 /* [AS] Initialize pv info list [HGM] and game status */
5764 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5765 pvInfoList[i].depth = 0;
5766 boards[i][EP_STATUS] = EP_NONE;
5767 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5770 initialRulePlies = 0; /* 50-move counter start */
5772 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5773 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5777 /* [HGM] logic here is completely changed. In stead of full positions */
5778 /* the initialized data only consist of the two backranks. The switch */
5779 /* selects which one we will use, which is than copied to the Board */
5780 /* initialPosition, which for the rest is initialized by Pawns and */
5781 /* empty squares. This initial position is then copied to boards[0], */
5782 /* possibly after shuffling, so that it remains available. */
5784 gameInfo.holdingsWidth = 0; /* default board sizes */
5785 gameInfo.boardWidth = 8;
5786 gameInfo.boardHeight = 8;
5787 gameInfo.holdingsSize = 0;
5788 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5789 for(i=0; i<BOARD_FILES-2; i++)
5790 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5791 initialPosition[EP_STATUS] = EP_NONE;
5792 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5793 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5794 SetCharTable(pieceNickName, appData.pieceNickNames);
5795 else SetCharTable(pieceNickName, "............");
5798 switch (gameInfo.variant) {
5799 case VariantFischeRandom:
5800 shuffleOpenings = TRUE;
5803 case VariantShatranj:
5804 pieces = ShatranjArray;
5805 nrCastlingRights = 0;
5806 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5809 pieces = makrukArray;
5810 nrCastlingRights = 0;
5811 startedFromSetupPosition = TRUE;
5812 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5814 case VariantTwoKings:
5815 pieces = twoKingsArray;
5818 pieces = GrandArray;
5819 nrCastlingRights = 0;
5820 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5821 gameInfo.boardWidth = 10;
5822 gameInfo.boardHeight = 10;
5823 gameInfo.holdingsSize = 7;
5825 case VariantCapaRandom:
5826 shuffleOpenings = TRUE;
5827 case VariantCapablanca:
5828 pieces = CapablancaArray;
5829 gameInfo.boardWidth = 10;
5830 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5833 pieces = GothicArray;
5834 gameInfo.boardWidth = 10;
5835 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5838 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5839 gameInfo.holdingsSize = 7;
5842 pieces = JanusArray;
5843 gameInfo.boardWidth = 10;
5844 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5845 nrCastlingRights = 6;
5846 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5847 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5848 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5849 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5850 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5851 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5854 pieces = FalconArray;
5855 gameInfo.boardWidth = 10;
5856 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5858 case VariantXiangqi:
5859 pieces = XiangqiArray;
5860 gameInfo.boardWidth = 9;
5861 gameInfo.boardHeight = 10;
5862 nrCastlingRights = 0;
5863 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5866 pieces = ShogiArray;
5867 gameInfo.boardWidth = 9;
5868 gameInfo.boardHeight = 9;
5869 gameInfo.holdingsSize = 7;
5870 nrCastlingRights = 0;
5871 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5873 case VariantCourier:
5874 pieces = CourierArray;
5875 gameInfo.boardWidth = 12;
5876 nrCastlingRights = 0;
5877 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5879 case VariantKnightmate:
5880 pieces = KnightmateArray;
5881 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5883 case VariantSpartan:
5884 pieces = SpartanArray;
5885 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5888 pieces = fairyArray;
5889 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5892 pieces = GreatArray;
5893 gameInfo.boardWidth = 10;
5894 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5895 gameInfo.holdingsSize = 8;
5899 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5900 gameInfo.holdingsSize = 8;
5901 startedFromSetupPosition = TRUE;
5903 case VariantCrazyhouse:
5904 case VariantBughouse:
5906 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5907 gameInfo.holdingsSize = 5;
5909 case VariantWildCastle:
5911 /* !!?shuffle with kings guaranteed to be on d or e file */
5912 shuffleOpenings = 1;
5914 case VariantNoCastle:
5916 nrCastlingRights = 0;
5917 /* !!?unconstrained back-rank shuffle */
5918 shuffleOpenings = 1;
5923 if(appData.NrFiles >= 0) {
5924 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5925 gameInfo.boardWidth = appData.NrFiles;
5927 if(appData.NrRanks >= 0) {
5928 gameInfo.boardHeight = appData.NrRanks;
5930 if(appData.holdingsSize >= 0) {
5931 i = appData.holdingsSize;
5932 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5933 gameInfo.holdingsSize = i;
5935 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5936 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5937 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5939 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5940 if(pawnRow < 1) pawnRow = 1;
5941 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5943 /* User pieceToChar list overrules defaults */
5944 if(appData.pieceToCharTable != NULL)
5945 SetCharTable(pieceToChar, appData.pieceToCharTable);
5947 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5949 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5950 s = (ChessSquare) 0; /* account holding counts in guard band */
5951 for( i=0; i<BOARD_HEIGHT; i++ )
5952 initialPosition[i][j] = s;
5954 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5955 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5956 initialPosition[pawnRow][j] = WhitePawn;
5957 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5958 if(gameInfo.variant == VariantXiangqi) {
5960 initialPosition[pawnRow][j] =
5961 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5962 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5963 initialPosition[2][j] = WhiteCannon;
5964 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5968 if(gameInfo.variant == VariantGrand) {
5969 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5970 initialPosition[0][j] = WhiteRook;
5971 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5974 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5976 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5979 initialPosition[1][j] = WhiteBishop;
5980 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5982 initialPosition[1][j] = WhiteRook;
5983 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5986 if( nrCastlingRights == -1) {
5987 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5988 /* This sets default castling rights from none to normal corners */
5989 /* Variants with other castling rights must set them themselves above */
5990 nrCastlingRights = 6;
5992 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5993 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5994 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5995 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5996 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5997 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6000 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6001 if(gameInfo.variant == VariantGreat) { // promotion commoners
6002 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6003 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6004 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6005 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6007 if( gameInfo.variant == VariantSChess ) {
6008 initialPosition[1][0] = BlackMarshall;
6009 initialPosition[2][0] = BlackAngel;
6010 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6011 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6012 initialPosition[1][1] = initialPosition[2][1] =
6013 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6015 if (appData.debugMode) {
6016 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6018 if(shuffleOpenings) {
6019 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6020 startedFromSetupPosition = TRUE;
6022 if(startedFromPositionFile) {
6023 /* [HGM] loadPos: use PositionFile for every new game */
6024 CopyBoard(initialPosition, filePosition);
6025 for(i=0; i<nrCastlingRights; i++)
6026 initialRights[i] = filePosition[CASTLING][i];
6027 startedFromSetupPosition = TRUE;
6030 CopyBoard(boards[0], initialPosition);
6032 if(oldx != gameInfo.boardWidth ||
6033 oldy != gameInfo.boardHeight ||
6034 oldv != gameInfo.variant ||
6035 oldh != gameInfo.holdingsWidth
6037 InitDrawingSizes(-2 ,0);
6039 oldv = gameInfo.variant;
6041 DrawPosition(TRUE, boards[currentMove]);
6045 SendBoard (ChessProgramState *cps, int moveNum)
6047 char message[MSG_SIZ];
6049 if (cps->useSetboard) {
6050 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6051 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6052 SendToProgram(message, cps);
6057 int i, j, left=0, right=BOARD_WIDTH;
6058 /* Kludge to set black to move, avoiding the troublesome and now
6059 * deprecated "black" command.
6061 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6062 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6064 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6066 SendToProgram("edit\n", cps);
6067 SendToProgram("#\n", cps);
6068 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6069 bp = &boards[moveNum][i][left];
6070 for (j = left; j < right; j++, bp++) {
6071 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6072 if ((int) *bp < (int) BlackPawn) {
6073 if(j == BOARD_RGHT+1)
6074 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6075 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6076 if(message[0] == '+' || message[0] == '~') {
6077 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078 PieceToChar((ChessSquare)(DEMOTED *bp)),
6081 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082 message[1] = BOARD_RGHT - 1 - j + '1';
6083 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6085 SendToProgram(message, cps);
6090 SendToProgram("c\n", cps);
6091 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6092 bp = &boards[moveNum][i][left];
6093 for (j = left; j < right; j++, bp++) {
6094 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6095 if (((int) *bp != (int) EmptySquare)
6096 && ((int) *bp >= (int) BlackPawn)) {
6097 if(j == BOARD_LEFT-2)
6098 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6099 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6101 if(message[0] == '+' || message[0] == '~') {
6102 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6103 PieceToChar((ChessSquare)(DEMOTED *bp)),
6106 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6107 message[1] = BOARD_RGHT - 1 - j + '1';
6108 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6110 SendToProgram(message, cps);
6115 SendToProgram(".\n", cps);
6117 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6120 char exclusionHeader[MSG_SIZ];
6121 int exCnt, excludePtr;
6122 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6123 static Exclusion excluTab[200];
6124 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6130 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6131 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6137 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6138 excludePtr = 24; exCnt = 0;
6143 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6144 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6145 char buf[2*MOVE_LEN], *p;
6146 Exclusion *e = excluTab;
6148 for(i=0; i<exCnt; i++)
6149 if(e[i].ff == fromX && e[i].fr == fromY &&
6150 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6151 if(i == exCnt) { // was not in exclude list; add it
6152 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6153 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6154 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6157 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6158 excludePtr++; e[i].mark = excludePtr++;
6159 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6162 exclusionHeader[e[i].mark] = state;
6166 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6167 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6171 if(promoChar == -1) { // kludge to indicate best move
6172 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6173 return 1; // if unparsable, abort
6175 // update exclusion map (resolving toggle by consulting existing state)
6176 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6178 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6179 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6180 excludeMap[k] |= 1<<j;
6181 else excludeMap[k] &= ~(1<<j);
6183 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6185 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6186 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6187 SendToProgram(buf, &first);
6188 return (state == '+');
6192 ExcludeClick (int index)
6195 Exclusion *e = excluTab;
6196 if(index < 25) { // none, best or tail clicked
6197 if(index < 13) { // none: include all
6198 WriteMap(0); // clear map
6199 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6200 SendToProgram("include all\n", &first); // and inform engine
6201 } else if(index > 18) { // tail
6202 if(exclusionHeader[19] == '-') { // tail was excluded
6203 SendToProgram("include all\n", &first);
6204 WriteMap(0); // clear map completely
6205 // now re-exclude selected moves
6206 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6207 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6208 } else { // tail was included or in mixed state
6209 SendToProgram("exclude all\n", &first);
6210 WriteMap(0xFF); // fill map completely
6211 // now re-include selected moves
6212 j = 0; // count them
6213 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6214 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6215 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6218 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6221 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6222 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6223 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6230 DefaultPromoChoice (int white)
6233 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6234 result = WhiteFerz; // no choice
6235 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6236 result= WhiteKing; // in Suicide Q is the last thing we want
6237 else if(gameInfo.variant == VariantSpartan)
6238 result = white ? WhiteQueen : WhiteAngel;
6239 else result = WhiteQueen;
6240 if(!white) result = WHITE_TO_BLACK result;
6244 static int autoQueen; // [HGM] oneclick
6247 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6249 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6250 /* [HGM] add Shogi promotions */
6251 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6256 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6257 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6259 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6260 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6263 piece = boards[currentMove][fromY][fromX];
6264 if(gameInfo.variant == VariantShogi) {
6265 promotionZoneSize = BOARD_HEIGHT/3;
6266 highestPromotingPiece = (int)WhiteFerz;
6267 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6268 promotionZoneSize = 3;
6271 // Treat Lance as Pawn when it is not representing Amazon
6272 if(gameInfo.variant != VariantSuper) {
6273 if(piece == WhiteLance) piece = WhitePawn; else
6274 if(piece == BlackLance) piece = BlackPawn;
6277 // next weed out all moves that do not touch the promotion zone at all
6278 if((int)piece >= BlackPawn) {
6279 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6281 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6283 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6284 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6287 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6289 // weed out mandatory Shogi promotions
6290 if(gameInfo.variant == VariantShogi) {
6291 if(piece >= BlackPawn) {
6292 if(toY == 0 && piece == BlackPawn ||
6293 toY == 0 && piece == BlackQueen ||
6294 toY <= 1 && piece == BlackKnight) {
6299 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6300 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6301 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6308 // weed out obviously illegal Pawn moves
6309 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6310 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6311 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6312 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6313 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6314 // note we are not allowed to test for valid (non-)capture, due to premove
6317 // we either have a choice what to promote to, or (in Shogi) whether to promote
6318 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6319 *promoChoice = PieceToChar(BlackFerz); // no choice
6322 // no sense asking what we must promote to if it is going to explode...
6323 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6324 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6327 // give caller the default choice even if we will not make it
6328 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6329 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6330 if( sweepSelect && gameInfo.variant != VariantGreat
6331 && gameInfo.variant != VariantGrand
6332 && gameInfo.variant != VariantSuper) return FALSE;
6333 if(autoQueen) return FALSE; // predetermined
6335 // suppress promotion popup on illegal moves that are not premoves
6336 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6337 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6338 if(appData.testLegality && !premove) {
6339 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6340 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6341 if(moveType != WhitePromotion && moveType != BlackPromotion)
6349 InPalace (int row, int column)
6350 { /* [HGM] for Xiangqi */
6351 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6352 column < (BOARD_WIDTH + 4)/2 &&
6353 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6358 PieceForSquare (int x, int y)
6360 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6363 return boards[currentMove][y][x];
6367 OKToStartUserMove (int x, int y)
6369 ChessSquare from_piece;
6372 if (matchMode) return FALSE;
6373 if (gameMode == EditPosition) return TRUE;
6375 if (x >= 0 && y >= 0)
6376 from_piece = boards[currentMove][y][x];
6378 from_piece = EmptySquare;
6380 if (from_piece == EmptySquare) return FALSE;
6382 white_piece = (int)from_piece >= (int)WhitePawn &&
6383 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6387 case TwoMachinesPlay:
6395 case MachinePlaysWhite:
6396 case IcsPlayingBlack:
6397 if (appData.zippyPlay) return FALSE;
6399 DisplayMoveError(_("You are playing Black"));
6404 case MachinePlaysBlack:
6405 case IcsPlayingWhite:
6406 if (appData.zippyPlay) return FALSE;
6408 DisplayMoveError(_("You are playing White"));
6413 case PlayFromGameFile:
6414 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6416 if (!white_piece && WhiteOnMove(currentMove)) {
6417 DisplayMoveError(_("It is White's turn"));
6420 if (white_piece && !WhiteOnMove(currentMove)) {
6421 DisplayMoveError(_("It is Black's turn"));
6424 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6425 /* Editing correspondence game history */
6426 /* Could disallow this or prompt for confirmation */
6431 case BeginningOfGame:
6432 if (appData.icsActive) return FALSE;
6433 if (!appData.noChessProgram) {
6435 DisplayMoveError(_("You are playing White"));
6442 if (!white_piece && WhiteOnMove(currentMove)) {
6443 DisplayMoveError(_("It is White's turn"));
6446 if (white_piece && !WhiteOnMove(currentMove)) {
6447 DisplayMoveError(_("It is Black's turn"));
6456 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6457 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6458 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6459 && gameMode != AnalyzeFile && gameMode != Training) {
6460 DisplayMoveError(_("Displayed position is not current"));
6467 OnlyMove (int *x, int *y, Boolean captures)
6469 DisambiguateClosure cl;
6470 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6472 case MachinePlaysBlack:
6473 case IcsPlayingWhite:
6474 case BeginningOfGame:
6475 if(!WhiteOnMove(currentMove)) return FALSE;
6477 case MachinePlaysWhite:
6478 case IcsPlayingBlack:
6479 if(WhiteOnMove(currentMove)) return FALSE;
6486 cl.pieceIn = EmptySquare;
6491 cl.promoCharIn = NULLCHAR;
6492 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6493 if( cl.kind == NormalMove ||
6494 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6495 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6496 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6503 if(cl.kind != ImpossibleMove) return FALSE;
6504 cl.pieceIn = EmptySquare;
6509 cl.promoCharIn = NULLCHAR;
6510 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6511 if( cl.kind == NormalMove ||
6512 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6513 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6514 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6519 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6525 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6526 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6527 int lastLoadGameUseList = FALSE;
6528 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6529 ChessMove lastLoadGameStart = EndOfFile;
6533 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6536 ChessSquare pdown, pup;
6537 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6540 /* Check if the user is playing in turn. This is complicated because we
6541 let the user "pick up" a piece before it is his turn. So the piece he
6542 tried to pick up may have been captured by the time he puts it down!
6543 Therefore we use the color the user is supposed to be playing in this
6544 test, not the color of the piece that is currently on the starting
6545 square---except in EditGame mode, where the user is playing both
6546 sides; fortunately there the capture race can't happen. (It can
6547 now happen in IcsExamining mode, but that's just too bad. The user
6548 will get a somewhat confusing message in that case.)
6553 case TwoMachinesPlay:
6557 /* We switched into a game mode where moves are not accepted,
6558 perhaps while the mouse button was down. */
6561 case MachinePlaysWhite:
6562 /* User is moving for Black */
6563 if (WhiteOnMove(currentMove)) {
6564 DisplayMoveError(_("It is White's turn"));
6569 case MachinePlaysBlack:
6570 /* User is moving for White */
6571 if (!WhiteOnMove(currentMove)) {
6572 DisplayMoveError(_("It is Black's turn"));
6577 case PlayFromGameFile:
6578 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6581 case BeginningOfGame:
6584 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6585 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6586 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6587 /* User is moving for Black */
6588 if (WhiteOnMove(currentMove)) {
6589 DisplayMoveError(_("It is White's turn"));
6593 /* User is moving for White */
6594 if (!WhiteOnMove(currentMove)) {
6595 DisplayMoveError(_("It is Black's turn"));
6601 case IcsPlayingBlack:
6602 /* User is moving for Black */
6603 if (WhiteOnMove(currentMove)) {
6604 if (!appData.premove) {
6605 DisplayMoveError(_("It is White's turn"));
6606 } else if (toX >= 0 && toY >= 0) {
6609 premoveFromX = fromX;
6610 premoveFromY = fromY;
6611 premovePromoChar = promoChar;
6613 if (appData.debugMode)
6614 fprintf(debugFP, "Got premove: fromX %d,"
6615 "fromY %d, toX %d, toY %d\n",
6616 fromX, fromY, toX, toY);
6622 case IcsPlayingWhite:
6623 /* User is moving for White */
6624 if (!WhiteOnMove(currentMove)) {
6625 if (!appData.premove) {
6626 DisplayMoveError(_("It is Black's turn"));
6627 } else if (toX >= 0 && toY >= 0) {
6630 premoveFromX = fromX;
6631 premoveFromY = fromY;
6632 premovePromoChar = promoChar;
6634 if (appData.debugMode)
6635 fprintf(debugFP, "Got premove: fromX %d,"
6636 "fromY %d, toX %d, toY %d\n",
6637 fromX, fromY, toX, toY);
6647 /* EditPosition, empty square, or different color piece;
6648 click-click move is possible */
6649 if (toX == -2 || toY == -2) {
6650 boards[0][fromY][fromX] = EmptySquare;
6651 DrawPosition(FALSE, boards[currentMove]);
6653 } else if (toX >= 0 && toY >= 0) {
6654 boards[0][toY][toX] = boards[0][fromY][fromX];
6655 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6656 if(boards[0][fromY][0] != EmptySquare) {
6657 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6658 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6661 if(fromX == BOARD_RGHT+1) {
6662 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6663 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6664 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6667 boards[0][fromY][fromX] = gatingPiece;
6668 DrawPosition(FALSE, boards[currentMove]);
6674 if(toX < 0 || toY < 0) return;
6675 pdown = boards[currentMove][fromY][fromX];
6676 pup = boards[currentMove][toY][toX];
6678 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6679 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6680 if( pup != EmptySquare ) return;
6681 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6682 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6683 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6684 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6685 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6686 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6687 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6691 /* [HGM] always test for legality, to get promotion info */
6692 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6693 fromY, fromX, toY, toX, promoChar);
6695 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6697 /* [HGM] but possibly ignore an IllegalMove result */
6698 if (appData.testLegality) {
6699 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6700 DisplayMoveError(_("Illegal move"));
6705 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6706 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6707 ClearPremoveHighlights(); // was included
6708 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6712 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6715 /* Common tail of UserMoveEvent and DropMenuEvent */
6717 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6721 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6722 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6723 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6724 if(WhiteOnMove(currentMove)) {
6725 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6727 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6731 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6732 move type in caller when we know the move is a legal promotion */
6733 if(moveType == NormalMove && promoChar)
6734 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6736 /* [HGM] <popupFix> The following if has been moved here from
6737 UserMoveEvent(). Because it seemed to belong here (why not allow
6738 piece drops in training games?), and because it can only be
6739 performed after it is known to what we promote. */
6740 if (gameMode == Training) {
6741 /* compare the move played on the board to the next move in the
6742 * game. If they match, display the move and the opponent's response.
6743 * If they don't match, display an error message.
6747 CopyBoard(testBoard, boards[currentMove]);
6748 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6750 if (CompareBoards(testBoard, boards[currentMove+1])) {
6751 ForwardInner(currentMove+1);
6753 /* Autoplay the opponent's response.
6754 * if appData.animate was TRUE when Training mode was entered,
6755 * the response will be animated.
6757 saveAnimate = appData.animate;
6758 appData.animate = animateTraining;
6759 ForwardInner(currentMove+1);
6760 appData.animate = saveAnimate;
6762 /* check for the end of the game */
6763 if (currentMove >= forwardMostMove) {
6764 gameMode = PlayFromGameFile;
6766 SetTrainingModeOff();
6767 DisplayInformation(_("End of game"));
6770 DisplayError(_("Incorrect move"), 0);
6775 /* Ok, now we know that the move is good, so we can kill
6776 the previous line in Analysis Mode */
6777 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6778 && currentMove < forwardMostMove) {
6779 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6780 else forwardMostMove = currentMove;
6785 /* If we need the chess program but it's dead, restart it */
6786 ResurrectChessProgram();
6788 /* A user move restarts a paused game*/
6792 thinkOutput[0] = NULLCHAR;
6794 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6796 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6797 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6801 if (gameMode == BeginningOfGame) {
6802 if (appData.noChessProgram) {
6803 gameMode = EditGame;
6807 gameMode = MachinePlaysBlack;
6810 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6812 if (first.sendName) {
6813 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6814 SendToProgram(buf, &first);
6821 /* Relay move to ICS or chess engine */
6822 if (appData.icsActive) {
6823 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6824 gameMode == IcsExamining) {
6825 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6826 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6828 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6830 // also send plain move, in case ICS does not understand atomic claims
6831 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6835 if (first.sendTime && (gameMode == BeginningOfGame ||
6836 gameMode == MachinePlaysWhite ||
6837 gameMode == MachinePlaysBlack)) {
6838 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6840 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6841 // [HGM] book: if program might be playing, let it use book
6842 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6843 first.maybeThinking = TRUE;
6844 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6845 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6846 SendBoard(&first, currentMove+1);
6847 } else SendMoveToProgram(forwardMostMove-1, &first);
6848 if (currentMove == cmailOldMove + 1) {
6849 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6853 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857 if(appData.testLegality)
6858 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6864 if (WhiteOnMove(currentMove)) {
6865 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6867 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6871 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6876 case MachinePlaysBlack:
6877 case MachinePlaysWhite:
6878 /* disable certain menu options while machine is thinking */
6879 SetMachineThinkingEnables();
6886 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6887 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6889 if(bookHit) { // [HGM] book: simulate book reply
6890 static char bookMove[MSG_SIZ]; // a bit generous?
6892 programStats.nodes = programStats.depth = programStats.time =
6893 programStats.score = programStats.got_only_move = 0;
6894 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6896 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6897 strcat(bookMove, bookHit);
6898 HandleMachineMove(bookMove, &first);
6904 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6906 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6907 Markers *m = (Markers *) closure;
6908 if(rf == fromY && ff == fromX)
6909 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6910 || kind == WhiteCapturesEnPassant
6911 || kind == BlackCapturesEnPassant);
6912 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6916 MarkTargetSquares (int clear)
6919 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6920 !appData.testLegality || gameMode == EditPosition) return;
6922 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6925 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6926 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6927 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6929 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6932 DrawPosition(TRUE, NULL);
6936 Explode (Board board, int fromX, int fromY, int toX, int toY)
6938 if(gameInfo.variant == VariantAtomic &&
6939 (board[toY][toX] != EmptySquare || // capture?
6940 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6941 board[fromY][fromX] == BlackPawn )
6943 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6949 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6952 CanPromote (ChessSquare piece, int y)
6954 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6955 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6956 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6957 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6958 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6959 gameInfo.variant == VariantMakruk) return FALSE;
6960 return (piece == BlackPawn && y == 1 ||
6961 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6962 piece == BlackLance && y == 1 ||
6963 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6967 LeftClick (ClickType clickType, int xPix, int yPix)
6970 Boolean saveAnimate;
6971 static int second = 0, promotionChoice = 0, clearFlag = 0;
6972 char promoChoice = NULLCHAR;
6974 static TimeMark lastClickTime, prevClickTime;
6976 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6978 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6980 if (clickType == Press) ErrorPopDown();
6982 x = EventToSquare(xPix, BOARD_WIDTH);
6983 y = EventToSquare(yPix, BOARD_HEIGHT);
6984 if (!flipView && y >= 0) {
6985 y = BOARD_HEIGHT - 1 - y;
6987 if (flipView && x >= 0) {
6988 x = BOARD_WIDTH - 1 - x;
6991 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6992 defaultPromoChoice = promoSweep;
6993 promoSweep = EmptySquare; // terminate sweep
6994 promoDefaultAltered = TRUE;
6995 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6998 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6999 if(clickType == Release) return; // ignore upclick of click-click destination
7000 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7001 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7002 if(gameInfo.holdingsWidth &&
7003 (WhiteOnMove(currentMove)
7004 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7005 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7006 // click in right holdings, for determining promotion piece
7007 ChessSquare p = boards[currentMove][y][x];
7008 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7009 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7010 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7011 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7016 DrawPosition(FALSE, boards[currentMove]);
7020 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7021 if(clickType == Press
7022 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7023 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7024 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7027 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7028 // could be static click on premove from-square: abort premove
7030 ClearPremoveHighlights();
7033 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7034 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7036 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7037 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7038 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7039 defaultPromoChoice = DefaultPromoChoice(side);
7042 autoQueen = appData.alwaysPromoteToQueen;
7046 gatingPiece = EmptySquare;
7047 if (clickType != Press) {
7048 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7049 DragPieceEnd(xPix, yPix); dragging = 0;
7050 DrawPosition(FALSE, NULL);
7054 doubleClick = FALSE;
7055 fromX = x; fromY = y; toX = toY = -1;
7056 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7057 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7058 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7060 if (OKToStartUserMove(fromX, fromY)) {
7062 MarkTargetSquares(0);
7063 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7064 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7065 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7066 promoSweep = defaultPromoChoice;
7067 selectFlag = 0; lastX = xPix; lastY = yPix;
7068 Sweep(0); // Pawn that is going to promote: preview promotion piece
7069 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7071 if (appData.highlightDragging) {
7072 SetHighlights(fromX, fromY, -1, -1);
7074 } else fromX = fromY = -1;
7080 if (clickType == Press && gameMode != EditPosition) {
7085 // ignore off-board to clicks
7086 if(y < 0 || x < 0) return;
7088 /* Check if clicking again on the same color piece */
7089 fromP = boards[currentMove][fromY][fromX];
7090 toP = boards[currentMove][y][x];
7091 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7092 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7093 WhitePawn <= toP && toP <= WhiteKing &&
7094 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7095 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7096 (BlackPawn <= fromP && fromP <= BlackKing &&
7097 BlackPawn <= toP && toP <= BlackKing &&
7098 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7099 !(fromP == BlackKing && toP == BlackRook && frc))) {
7100 /* Clicked again on same color piece -- changed his mind */
7101 second = (x == fromX && y == fromY);
7102 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7103 second = FALSE; // first double-click rather than scond click
7104 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7106 promoDefaultAltered = FALSE;
7107 MarkTargetSquares(1);
7108 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7109 if (appData.highlightDragging) {
7110 SetHighlights(x, y, -1, -1);
7114 if (OKToStartUserMove(x, y)) {
7115 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7116 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7117 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7118 gatingPiece = boards[currentMove][fromY][fromX];
7119 else gatingPiece = doubleClick ? fromP : EmptySquare;
7121 fromY = y; dragging = 1;
7122 MarkTargetSquares(0);
7123 DragPieceBegin(xPix, yPix, FALSE);
7124 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7125 promoSweep = defaultPromoChoice;
7126 selectFlag = 0; lastX = xPix; lastY = yPix;
7127 Sweep(0); // Pawn that is going to promote: preview promotion piece
7131 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7134 // ignore clicks on holdings
7135 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7138 if (clickType == Release && x == fromX && y == fromY) {
7139 DragPieceEnd(xPix, yPix); dragging = 0;
7141 // a deferred attempt to click-click move an empty square on top of a piece
7142 boards[currentMove][y][x] = EmptySquare;
7144 DrawPosition(FALSE, boards[currentMove]);
7145 fromX = fromY = -1; clearFlag = 0;
7148 if (appData.animateDragging) {
7149 /* Undo animation damage if any */
7150 DrawPosition(FALSE, NULL);
7153 /* Second up/down in same square; just abort move */
7156 gatingPiece = EmptySquare;
7159 ClearPremoveHighlights();
7161 /* First upclick in same square; start click-click mode */
7162 SetHighlights(x, y, -1, -1);
7169 /* we now have a different from- and (possibly off-board) to-square */
7170 /* Completed move */
7173 saveAnimate = appData.animate;
7174 MarkTargetSquares(1);
7175 if (clickType == Press) {
7176 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7177 // must be Edit Position mode with empty-square selected
7178 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7179 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7182 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7183 ChessSquare piece = boards[currentMove][fromY][fromX];
7184 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7185 promoSweep = defaultPromoChoice;
7186 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7187 selectFlag = 0; lastX = xPix; lastY = yPix;
7188 Sweep(0); // Pawn that is going to promote: preview promotion piece
7189 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7190 DrawPosition(FALSE, boards[currentMove]);
7193 /* Finish clickclick move */
7194 if (appData.animate || appData.highlightLastMove) {
7195 SetHighlights(fromX, fromY, toX, toY);
7200 /* Finish drag move */
7201 if (appData.highlightLastMove) {
7202 SetHighlights(fromX, fromY, toX, toY);
7206 DragPieceEnd(xPix, yPix); dragging = 0;
7207 /* Don't animate move and drag both */
7208 appData.animate = FALSE;
7211 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7212 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7213 ChessSquare piece = boards[currentMove][fromY][fromX];
7214 if(gameMode == EditPosition && piece != EmptySquare &&
7215 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7218 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7219 n = PieceToNumber(piece - (int)BlackPawn);
7220 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7221 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7222 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7224 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7225 n = PieceToNumber(piece);
7226 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7227 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7228 boards[currentMove][n][BOARD_WIDTH-2]++;
7230 boards[currentMove][fromY][fromX] = EmptySquare;
7234 DrawPosition(TRUE, boards[currentMove]);
7238 // off-board moves should not be highlighted
7239 if(x < 0 || y < 0) ClearHighlights();
7241 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7243 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7244 SetHighlights(fromX, fromY, toX, toY);
7245 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7246 // [HGM] super: promotion to captured piece selected from holdings
7247 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7248 promotionChoice = TRUE;
7249 // kludge follows to temporarily execute move on display, without promoting yet
7250 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7251 boards[currentMove][toY][toX] = p;
7252 DrawPosition(FALSE, boards[currentMove]);
7253 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7254 boards[currentMove][toY][toX] = q;
7255 DisplayMessage("Click in holdings to choose piece", "");
7260 int oldMove = currentMove;
7261 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7262 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7263 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7264 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7265 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7266 DrawPosition(TRUE, boards[currentMove]);
7269 appData.animate = saveAnimate;
7270 if (appData.animate || appData.animateDragging) {
7271 /* Undo animation damage if needed */
7272 DrawPosition(FALSE, NULL);
7277 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7278 { // front-end-free part taken out of PieceMenuPopup
7279 int whichMenu; int xSqr, ySqr;
7281 if(seekGraphUp) { // [HGM] seekgraph
7282 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7283 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7287 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7288 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7289 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7290 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7291 if(action == Press) {
7292 originalFlip = flipView;
7293 flipView = !flipView; // temporarily flip board to see game from partners perspective
7294 DrawPosition(TRUE, partnerBoard);
7295 DisplayMessage(partnerStatus, "");
7297 } else if(action == Release) {
7298 flipView = originalFlip;
7299 DrawPosition(TRUE, boards[currentMove]);
7305 xSqr = EventToSquare(x, BOARD_WIDTH);
7306 ySqr = EventToSquare(y, BOARD_HEIGHT);
7307 if (action == Release) {
7308 if(pieceSweep != EmptySquare) {
7309 EditPositionMenuEvent(pieceSweep, toX, toY);
7310 pieceSweep = EmptySquare;
7311 } else UnLoadPV(); // [HGM] pv
7313 if (action != Press) return -2; // return code to be ignored
7316 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7318 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7319 if (xSqr < 0 || ySqr < 0) return -1;
7320 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7321 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7322 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7323 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7327 if(!appData.icsEngineAnalyze) return -1;
7328 case IcsPlayingWhite:
7329 case IcsPlayingBlack:
7330 if(!appData.zippyPlay) goto noZip;
7333 case MachinePlaysWhite:
7334 case MachinePlaysBlack:
7335 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7336 if (!appData.dropMenu) {
7338 return 2; // flag front-end to grab mouse events
7340 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7341 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7344 if (xSqr < 0 || ySqr < 0) return -1;
7345 if (!appData.dropMenu || appData.testLegality &&
7346 gameInfo.variant != VariantBughouse &&
7347 gameInfo.variant != VariantCrazyhouse) return -1;
7348 whichMenu = 1; // drop menu
7354 if (((*fromX = xSqr) < 0) ||
7355 ((*fromY = ySqr) < 0)) {
7356 *fromX = *fromY = -1;
7360 *fromX = BOARD_WIDTH - 1 - *fromX;
7362 *fromY = BOARD_HEIGHT - 1 - *fromY;
7368 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7370 // char * hint = lastHint;
7371 FrontEndProgramStats stats;
7373 stats.which = cps == &first ? 0 : 1;
7374 stats.depth = cpstats->depth;
7375 stats.nodes = cpstats->nodes;
7376 stats.score = cpstats->score;
7377 stats.time = cpstats->time;
7378 stats.pv = cpstats->movelist;
7379 stats.hint = lastHint;
7380 stats.an_move_index = 0;
7381 stats.an_move_count = 0;
7383 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7384 stats.hint = cpstats->move_name;
7385 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7386 stats.an_move_count = cpstats->nr_moves;
7389 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
7391 SetProgramStats( &stats );
7395 ClearEngineOutputPane (int which)
7397 static FrontEndProgramStats dummyStats;
7398 dummyStats.which = which;
7399 dummyStats.pv = "#";
7400 SetProgramStats( &dummyStats );
7403 #define MAXPLAYERS 500
7406 TourneyStandings (int display)
7408 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7409 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7410 char result, *p, *names[MAXPLAYERS];
7412 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7413 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7414 names[0] = p = strdup(appData.participants);
7415 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7417 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7419 while(result = appData.results[nr]) {
7420 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7421 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7422 wScore = bScore = 0;
7424 case '+': wScore = 2; break;
7425 case '-': bScore = 2; break;
7426 case '=': wScore = bScore = 1; break;
7428 case '*': return strdup("busy"); // tourney not finished
7436 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7437 for(w=0; w<nPlayers; w++) {
7439 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7440 ranking[w] = b; points[w] = bScore; score[b] = -2;
7442 p = malloc(nPlayers*34+1);
7443 for(w=0; w<nPlayers && w<display; w++)
7444 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7450 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7451 { // count all piece types
7453 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7454 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7455 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7458 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7459 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7460 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7461 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7462 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7463 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7468 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7470 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7471 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7473 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7474 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7475 if(myPawns == 2 && nMine == 3) // KPP
7476 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7477 if(myPawns == 1 && nMine == 2) // KP
7478 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7479 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7480 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7481 if(myPawns) return FALSE;
7482 if(pCnt[WhiteRook+side])
7483 return pCnt[BlackRook-side] ||
7484 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7485 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7486 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7487 if(pCnt[WhiteCannon+side]) {
7488 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7489 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7491 if(pCnt[WhiteKnight+side])
7492 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7497 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7499 VariantClass v = gameInfo.variant;
7501 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7502 if(v == VariantShatranj) return TRUE; // always winnable through baring
7503 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7504 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7506 if(v == VariantXiangqi) {
7507 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7509 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7510 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7511 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7512 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7513 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7514 if(stale) // we have at least one last-rank P plus perhaps C
7515 return majors // KPKX
7516 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7518 return pCnt[WhiteFerz+side] // KCAK
7519 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7520 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7521 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7523 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7524 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7526 if(nMine == 1) return FALSE; // bare King
7527 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
7528 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7529 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7530 // by now we have King + 1 piece (or multiple Bishops on the same color)
7531 if(pCnt[WhiteKnight+side])
7532 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7533 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7534 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7536 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7537 if(pCnt[WhiteAlfil+side])
7538 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7539 if(pCnt[WhiteWazir+side])
7540 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7547 CompareWithRights (Board b1, Board b2)
7550 if(!CompareBoards(b1, b2)) return FALSE;
7551 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7552 /* compare castling rights */
7553 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7554 rights++; /* King lost rights, while rook still had them */
7555 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7556 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7557 rights++; /* but at least one rook lost them */
7559 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7561 if( b1[CASTLING][5] != NoRights ) {
7562 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7569 Adjudicate (ChessProgramState *cps)
7570 { // [HGM] some adjudications useful with buggy engines
7571 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7572 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7573 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7574 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7575 int k, count = 0; static int bare = 1;
7576 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7577 Boolean canAdjudicate = !appData.icsActive;
7579 // most tests only when we understand the game, i.e. legality-checking on
7580 if( appData.testLegality )
7581 { /* [HGM] Some more adjudications for obstinate engines */
7582 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7583 static int moveCount = 6;
7585 char *reason = NULL;
7587 /* Count what is on board. */
7588 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7590 /* Some material-based adjudications that have to be made before stalemate test */
7591 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7592 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7593 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7594 if(canAdjudicate && appData.checkMates) {
7596 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7597 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7598 "Xboard adjudication: King destroyed", GE_XBOARD );
7603 /* Bare King in Shatranj (loses) or Losers (wins) */
7604 if( nrW == 1 || nrB == 1) {
7605 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7606 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7607 if(canAdjudicate && appData.checkMates) {
7609 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7610 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7611 "Xboard adjudication: Bare king", GE_XBOARD );
7615 if( gameInfo.variant == VariantShatranj && --bare < 0)
7617 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7618 if(canAdjudicate && appData.checkMates) {
7619 /* but only adjudicate if adjudication enabled */
7621 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7622 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7623 "Xboard adjudication: Bare king", GE_XBOARD );
7630 // don't wait for engine to announce game end if we can judge ourselves
7631 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7633 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7634 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7635 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7636 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7639 reason = "Xboard adjudication: 3rd check";
7640 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7650 reason = "Xboard adjudication: Stalemate";
7651 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7652 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7653 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7654 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7655 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7656 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7657 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7658 EP_CHECKMATE : EP_WINS);
7659 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7660 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7664 reason = "Xboard adjudication: Checkmate";
7665 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7669 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7671 result = GameIsDrawn; break;
7673 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7675 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7679 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7681 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7682 GameEnds( result, reason, GE_XBOARD );
7686 /* Next absolutely insufficient mating material. */
7687 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7688 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7689 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7691 /* always flag draws, for judging claims */
7692 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7694 if(canAdjudicate && appData.materialDraws) {
7695 /* but only adjudicate them if adjudication enabled */
7696 if(engineOpponent) {
7697 SendToProgram("force\n", engineOpponent); // suppress reply
7698 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7700 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7705 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7706 if(gameInfo.variant == VariantXiangqi ?
7707 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7709 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7710 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7711 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7712 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7714 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7715 { /* if the first 3 moves do not show a tactical win, declare draw */
7716 if(engineOpponent) {
7717 SendToProgram("force\n", engineOpponent); // suppress reply
7718 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7720 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7723 } else moveCount = 6;
7726 // Repetition draws and 50-move rule can be applied independently of legality testing
7728 /* Check for rep-draws */
7730 for(k = forwardMostMove-2;
7731 k>=backwardMostMove && k>=forwardMostMove-100 &&
7732 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7733 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7736 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7737 /* compare castling rights */
7738 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7739 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7740 rights++; /* King lost rights, while rook still had them */
7741 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7742 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7743 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7744 rights++; /* but at least one rook lost them */
7746 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7747 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7749 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7750 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7751 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7754 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7755 && appData.drawRepeats > 1) {
7756 /* adjudicate after user-specified nr of repeats */
7757 int result = GameIsDrawn;
7758 char *details = "XBoard adjudication: repetition draw";
7759 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7760 // [HGM] xiangqi: check for forbidden perpetuals
7761 int m, ourPerpetual = 1, hisPerpetual = 1;
7762 for(m=forwardMostMove; m>k; m-=2) {
7763 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7764 ourPerpetual = 0; // the current mover did not always check
7765 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7766 hisPerpetual = 0; // the opponent did not always check
7768 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7769 ourPerpetual, hisPerpetual);
7770 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7771 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7772 details = "Xboard adjudication: perpetual checking";
7774 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7775 break; // (or we would have caught him before). Abort repetition-checking loop.
7777 // Now check for perpetual chases
7778 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7779 hisPerpetual = PerpetualChase(k, forwardMostMove);
7780 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7781 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7782 static char resdet[MSG_SIZ];
7783 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7785 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7787 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7788 break; // Abort repetition-checking loop.
7790 // if neither of us is checking or chasing all the time, or both are, it is draw
7792 if(engineOpponent) {
7793 SendToProgram("force\n", engineOpponent); // suppress reply
7794 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7796 GameEnds( result, details, GE_XBOARD );
7799 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7800 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7804 /* Now we test for 50-move draws. Determine ply count */
7805 count = forwardMostMove;
7806 /* look for last irreversble move */
7807 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7809 /* if we hit starting position, add initial plies */
7810 if( count == backwardMostMove )
7811 count -= initialRulePlies;
7812 count = forwardMostMove - count;
7813 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7814 // adjust reversible move counter for checks in Xiangqi
7815 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7816 if(i < backwardMostMove) i = backwardMostMove;
7817 while(i <= forwardMostMove) {
7818 lastCheck = inCheck; // check evasion does not count
7819 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7820 if(inCheck || lastCheck) count--; // check does not count
7825 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7826 /* this is used to judge if draw claims are legal */
7827 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7828 if(engineOpponent) {
7829 SendToProgram("force\n", engineOpponent); // suppress reply
7830 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7832 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7836 /* if draw offer is pending, treat it as a draw claim
7837 * when draw condition present, to allow engines a way to
7838 * claim draws before making their move to avoid a race
7839 * condition occurring after their move
7841 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7843 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7844 p = "Draw claim: 50-move rule";
7845 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7846 p = "Draw claim: 3-fold repetition";
7847 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7848 p = "Draw claim: insufficient mating material";
7849 if( p != NULL && canAdjudicate) {
7850 if(engineOpponent) {
7851 SendToProgram("force\n", engineOpponent); // suppress reply
7852 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7854 GameEnds( GameIsDrawn, p, GE_XBOARD );
7859 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7860 if(engineOpponent) {
7861 SendToProgram("force\n", engineOpponent); // suppress reply
7862 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7864 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7871 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7872 { // [HGM] book: this routine intercepts moves to simulate book replies
7873 char *bookHit = NULL;
7875 //first determine if the incoming move brings opponent into his book
7876 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7877 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7878 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7879 if(bookHit != NULL && !cps->bookSuspend) {
7880 // make sure opponent is not going to reply after receiving move to book position
7881 SendToProgram("force\n", cps);
7882 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7884 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7885 // now arrange restart after book miss
7887 // after a book hit we never send 'go', and the code after the call to this routine
7888 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7889 char buf[MSG_SIZ], *move = bookHit;
7891 int fromX, fromY, toX, toY;
7895 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7896 &fromX, &fromY, &toX, &toY, &promoChar)) {
7897 (void) CoordsToAlgebraic(boards[forwardMostMove],
7898 PosFlags(forwardMostMove),
7899 fromY, fromX, toY, toX, promoChar, move);
7901 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7905 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7906 SendToProgram(buf, cps);
7907 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7908 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7909 SendToProgram("go\n", cps);
7910 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7911 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7912 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7913 SendToProgram("go\n", cps);
7914 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7916 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7920 ChessProgramState *savedState;
7922 DeferredBookMove (void)
7924 if(savedState->lastPing != savedState->lastPong)
7925 ScheduleDelayedEvent(DeferredBookMove, 10);
7927 HandleMachineMove(savedMessage, savedState);
7930 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7933 HandleMachineMove (char *message, ChessProgramState *cps)
7935 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7936 char realname[MSG_SIZ];
7937 int fromX, fromY, toX, toY;
7944 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7945 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7946 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7947 DisplayError(_("Invalid pairing from pairing engine"), 0);
7950 pairingReceived = 1;
7952 return; // Skim the pairing messages here.
7957 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7959 * Kludge to ignore BEL characters
7961 while (*message == '\007') message++;
7964 * [HGM] engine debug message: ignore lines starting with '#' character
7966 if(cps->debug && *message == '#') return;
7969 * Look for book output
7971 if (cps == &first && bookRequested) {
7972 if (message[0] == '\t' || message[0] == ' ') {
7973 /* Part of the book output is here; append it */
7974 strcat(bookOutput, message);
7975 strcat(bookOutput, " \n");
7977 } else if (bookOutput[0] != NULLCHAR) {
7978 /* All of book output has arrived; display it */
7979 char *p = bookOutput;
7980 while (*p != NULLCHAR) {
7981 if (*p == '\t') *p = ' ';
7984 DisplayInformation(bookOutput);
7985 bookRequested = FALSE;
7986 /* Fall through to parse the current output */
7991 * Look for machine move.
7993 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7994 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7996 /* This method is only useful on engines that support ping */
7997 if (cps->lastPing != cps->lastPong) {
7998 if (gameMode == BeginningOfGame) {
7999 /* Extra move from before last new; ignore */
8000 if (appData.debugMode) {
8001 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8004 if (appData.debugMode) {
8005 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8006 cps->which, gameMode);
8009 SendToProgram("undo\n", cps);
8015 case BeginningOfGame:
8016 /* Extra move from before last reset; ignore */
8017 if (appData.debugMode) {
8018 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8025 /* Extra move after we tried to stop. The mode test is
8026 not a reliable way of detecting this problem, but it's
8027 the best we can do on engines that don't support ping.
8029 if (appData.debugMode) {
8030 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8031 cps->which, gameMode);
8033 SendToProgram("undo\n", cps);
8036 case MachinePlaysWhite:
8037 case IcsPlayingWhite:
8038 machineWhite = TRUE;
8041 case MachinePlaysBlack:
8042 case IcsPlayingBlack:
8043 machineWhite = FALSE;
8046 case TwoMachinesPlay:
8047 machineWhite = (cps->twoMachinesColor[0] == 'w');
8050 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8051 if (appData.debugMode) {
8053 "Ignoring move out of turn by %s, gameMode %d"
8054 ", forwardMost %d\n",
8055 cps->which, gameMode, forwardMostMove);
8060 if(cps->alphaRank) AlphaRank(machineMove, 4);
8061 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8062 &fromX, &fromY, &toX, &toY, &promoChar)) {
8063 /* Machine move could not be parsed; ignore it. */
8064 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8065 machineMove, _(cps->which));
8066 DisplayError(buf1, 0);
8067 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8068 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8069 if (gameMode == TwoMachinesPlay) {
8070 GameEnds(machineWhite ? BlackWins : WhiteWins,
8076 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8077 /* So we have to redo legality test with true e.p. status here, */
8078 /* to make sure an illegal e.p. capture does not slip through, */
8079 /* to cause a forfeit on a justified illegal-move complaint */
8080 /* of the opponent. */
8081 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8083 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8084 fromY, fromX, toY, toX, promoChar);
8085 if(moveType == IllegalMove) {
8086 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8087 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8088 GameEnds(machineWhite ? BlackWins : WhiteWins,
8091 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8092 /* [HGM] Kludge to handle engines that send FRC-style castling
8093 when they shouldn't (like TSCP-Gothic) */
8095 case WhiteASideCastleFR:
8096 case BlackASideCastleFR:
8098 currentMoveString[2]++;
8100 case WhiteHSideCastleFR:
8101 case BlackHSideCastleFR:
8103 currentMoveString[2]--;
8105 default: ; // nothing to do, but suppresses warning of pedantic compilers
8108 hintRequested = FALSE;
8109 lastHint[0] = NULLCHAR;
8110 bookRequested = FALSE;
8111 /* Program may be pondering now */
8112 cps->maybeThinking = TRUE;
8113 if (cps->sendTime == 2) cps->sendTime = 1;
8114 if (cps->offeredDraw) cps->offeredDraw--;
8116 /* [AS] Save move info*/
8117 pvInfoList[ forwardMostMove ].score = programStats.score;
8118 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8119 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8121 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8123 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8124 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8127 while( count < adjudicateLossPlies ) {
8128 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8131 score = -score; /* Flip score for winning side */
8134 if( score > adjudicateLossThreshold ) {
8141 if( count >= adjudicateLossPlies ) {
8142 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8144 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8145 "Xboard adjudication",
8152 if(Adjudicate(cps)) {
8153 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8154 return; // [HGM] adjudicate: for all automatic game ends
8158 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8160 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8161 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8163 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8165 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8167 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8168 char buf[3*MSG_SIZ];
8170 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8171 programStats.score / 100.,
8173 programStats.time / 100.,
8174 (unsigned int)programStats.nodes,
8175 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8176 programStats.movelist);
8178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8183 /* [AS] Clear stats for next move */
8184 ClearProgramStats();
8185 thinkOutput[0] = NULLCHAR;
8186 hiddenThinkOutputState = 0;
8189 if (gameMode == TwoMachinesPlay) {
8190 /* [HGM] relaying draw offers moved to after reception of move */
8191 /* and interpreting offer as claim if it brings draw condition */
8192 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8193 SendToProgram("draw\n", cps->other);
8195 if (cps->other->sendTime) {
8196 SendTimeRemaining(cps->other,
8197 cps->other->twoMachinesColor[0] == 'w');
8199 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8200 if (firstMove && !bookHit) {
8202 if (cps->other->useColors) {
8203 SendToProgram(cps->other->twoMachinesColor, cps->other);
8205 SendToProgram("go\n", cps->other);
8207 cps->other->maybeThinking = TRUE;
8210 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8212 if (!pausing && appData.ringBellAfterMoves) {
8217 * Reenable menu items that were disabled while
8218 * machine was thinking
8220 if (gameMode != TwoMachinesPlay)
8221 SetUserThinkingEnables();
8223 // [HGM] book: after book hit opponent has received move and is now in force mode
8224 // force the book reply into it, and then fake that it outputted this move by jumping
8225 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8227 static char bookMove[MSG_SIZ]; // a bit generous?
8229 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8230 strcat(bookMove, bookHit);
8233 programStats.nodes = programStats.depth = programStats.time =
8234 programStats.score = programStats.got_only_move = 0;
8235 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8237 if(cps->lastPing != cps->lastPong) {
8238 savedMessage = message; // args for deferred call
8240 ScheduleDelayedEvent(DeferredBookMove, 10);
8249 /* Set special modes for chess engines. Later something general
8250 * could be added here; for now there is just one kludge feature,
8251 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8252 * when "xboard" is given as an interactive command.
8254 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8255 cps->useSigint = FALSE;
8256 cps->useSigterm = FALSE;
8258 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8259 ParseFeatures(message+8, cps);
8260 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8263 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8264 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8265 int dummy, s=6; char buf[MSG_SIZ];
8266 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8267 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8268 if(startedFromSetupPosition) return;
8269 ParseFEN(boards[0], &dummy, message+s);
8270 DrawPosition(TRUE, boards[0]);
8271 startedFromSetupPosition = TRUE;
8274 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8275 * want this, I was asked to put it in, and obliged.
8277 if (!strncmp(message, "setboard ", 9)) {
8278 Board initial_position;
8280 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8282 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8283 DisplayError(_("Bad FEN received from engine"), 0);
8287 CopyBoard(boards[0], initial_position);
8288 initialRulePlies = FENrulePlies;
8289 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8290 else gameMode = MachinePlaysBlack;
8291 DrawPosition(FALSE, boards[currentMove]);
8297 * Look for communication commands
8299 if (!strncmp(message, "telluser ", 9)) {
8300 if(message[9] == '\\' && message[10] == '\\')
8301 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8303 DisplayNote(message + 9);
8306 if (!strncmp(message, "tellusererror ", 14)) {
8308 if(message[14] == '\\' && message[15] == '\\')
8309 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8311 DisplayError(message + 14, 0);
8314 if (!strncmp(message, "tellopponent ", 13)) {
8315 if (appData.icsActive) {
8317 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8321 DisplayNote(message + 13);
8325 if (!strncmp(message, "tellothers ", 11)) {
8326 if (appData.icsActive) {
8328 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8334 if (!strncmp(message, "tellall ", 8)) {
8335 if (appData.icsActive) {
8337 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8341 DisplayNote(message + 8);
8345 if (strncmp(message, "warning", 7) == 0) {
8346 /* Undocumented feature, use tellusererror in new code */
8347 DisplayError(message, 0);
8350 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8351 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8352 strcat(realname, " query");
8353 AskQuestion(realname, buf2, buf1, cps->pr);
8356 /* Commands from the engine directly to ICS. We don't allow these to be
8357 * sent until we are logged on. Crafty kibitzes have been known to
8358 * interfere with the login process.
8361 if (!strncmp(message, "tellics ", 8)) {
8362 SendToICS(message + 8);
8366 if (!strncmp(message, "tellicsnoalias ", 15)) {
8367 SendToICS(ics_prefix);
8368 SendToICS(message + 15);
8372 /* The following are for backward compatibility only */
8373 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8374 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8375 SendToICS(ics_prefix);
8381 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8385 * If the move is illegal, cancel it and redraw the board.
8386 * Also deal with other error cases. Matching is rather loose
8387 * here to accommodate engines written before the spec.
8389 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8390 strncmp(message, "Error", 5) == 0) {
8391 if (StrStr(message, "name") ||
8392 StrStr(message, "rating") || StrStr(message, "?") ||
8393 StrStr(message, "result") || StrStr(message, "board") ||
8394 StrStr(message, "bk") || StrStr(message, "computer") ||
8395 StrStr(message, "variant") || StrStr(message, "hint") ||
8396 StrStr(message, "random") || StrStr(message, "depth") ||
8397 StrStr(message, "accepted")) {
8400 if (StrStr(message, "protover")) {
8401 /* Program is responding to input, so it's apparently done
8402 initializing, and this error message indicates it is
8403 protocol version 1. So we don't need to wait any longer
8404 for it to initialize and send feature commands. */
8405 FeatureDone(cps, 1);
8406 cps->protocolVersion = 1;
8409 cps->maybeThinking = FALSE;
8411 if (StrStr(message, "draw")) {
8412 /* Program doesn't have "draw" command */
8413 cps->sendDrawOffers = 0;
8416 if (cps->sendTime != 1 &&
8417 (StrStr(message, "time") || StrStr(message, "otim"))) {
8418 /* Program apparently doesn't have "time" or "otim" command */
8422 if (StrStr(message, "analyze")) {
8423 cps->analysisSupport = FALSE;
8424 cps->analyzing = FALSE;
8425 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8426 EditGameEvent(); // [HGM] try to preserve loaded game
8427 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8428 DisplayError(buf2, 0);
8431 if (StrStr(message, "(no matching move)st")) {
8432 /* Special kludge for GNU Chess 4 only */
8433 cps->stKludge = TRUE;
8434 SendTimeControl(cps, movesPerSession, timeControl,
8435 timeIncrement, appData.searchDepth,
8439 if (StrStr(message, "(no matching move)sd")) {
8440 /* Special kludge for GNU Chess 4 only */
8441 cps->sdKludge = TRUE;
8442 SendTimeControl(cps, movesPerSession, timeControl,
8443 timeIncrement, appData.searchDepth,
8447 if (!StrStr(message, "llegal")) {
8450 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8451 gameMode == IcsIdle) return;
8452 if (forwardMostMove <= backwardMostMove) return;
8453 if (pausing) PauseEvent();
8454 if(appData.forceIllegal) {
8455 // [HGM] illegal: machine refused move; force position after move into it
8456 SendToProgram("force\n", cps);
8457 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8458 // we have a real problem now, as SendBoard will use the a2a3 kludge
8459 // when black is to move, while there might be nothing on a2 or black
8460 // might already have the move. So send the board as if white has the move.
8461 // But first we must change the stm of the engine, as it refused the last move
8462 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8463 if(WhiteOnMove(forwardMostMove)) {
8464 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8465 SendBoard(cps, forwardMostMove); // kludgeless board
8467 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8468 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8469 SendBoard(cps, forwardMostMove+1); // kludgeless board
8471 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8472 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8473 gameMode == TwoMachinesPlay)
8474 SendToProgram("go\n", cps);
8477 if (gameMode == PlayFromGameFile) {
8478 /* Stop reading this game file */
8479 gameMode = EditGame;
8482 /* [HGM] illegal-move claim should forfeit game when Xboard */
8483 /* only passes fully legal moves */
8484 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8485 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8486 "False illegal-move claim", GE_XBOARD );
8487 return; // do not take back move we tested as valid
8489 currentMove = forwardMostMove-1;
8490 DisplayMove(currentMove-1); /* before DisplayMoveError */
8491 SwitchClocks(forwardMostMove-1); // [HGM] race
8492 DisplayBothClocks();
8493 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8494 parseList[currentMove], _(cps->which));
8495 DisplayMoveError(buf1);
8496 DrawPosition(FALSE, boards[currentMove]);
8498 SetUserThinkingEnables();
8501 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8502 /* Program has a broken "time" command that
8503 outputs a string not ending in newline.
8509 * If chess program startup fails, exit with an error message.
8510 * Attempts to recover here are futile. [HGM] Well, we try anyway
8512 if ((StrStr(message, "unknown host") != NULL)
8513 || (StrStr(message, "No remote directory") != NULL)
8514 || (StrStr(message, "not found") != NULL)
8515 || (StrStr(message, "No such file") != NULL)
8516 || (StrStr(message, "can't alloc") != NULL)
8517 || (StrStr(message, "Permission denied") != NULL)) {
8519 cps->maybeThinking = FALSE;
8520 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8521 _(cps->which), cps->program, cps->host, message);
8522 RemoveInputSource(cps->isr);
8523 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8525 DestroyChildProcess(cps->pr, 9 ); // just to be sure
8528 appData.noChessProgram = TRUE;
8529 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8530 gameMode = BeginningOfGame; ModeHighlight();
8533 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8534 DisplayMessage("", ""); // erase waiting message
8535 DisplayError(buf1, 0);
8541 * Look for hint output
8543 if (sscanf(message, "Hint: %s", buf1) == 1) {
8544 if (cps == &first && hintRequested) {
8545 hintRequested = FALSE;
8546 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8547 &fromX, &fromY, &toX, &toY, &promoChar)) {
8548 (void) CoordsToAlgebraic(boards[forwardMostMove],
8549 PosFlags(forwardMostMove),
8550 fromY, fromX, toY, toX, promoChar, buf1);
8551 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8552 DisplayInformation(buf2);
8554 /* Hint move could not be parsed!? */
8555 snprintf(buf2, sizeof(buf2),
8556 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8557 buf1, _(cps->which));
8558 DisplayError(buf2, 0);
8561 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8567 * Ignore other messages if game is not in progress
8569 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8570 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8573 * look for win, lose, draw, or draw offer
8575 if (strncmp(message, "1-0", 3) == 0) {
8576 char *p, *q, *r = "";
8577 p = strchr(message, '{');
8585 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8587 } else if (strncmp(message, "0-1", 3) == 0) {
8588 char *p, *q, *r = "";
8589 p = strchr(message, '{');
8597 /* Kludge for Arasan 4.1 bug */
8598 if (strcmp(r, "Black resigns") == 0) {
8599 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8602 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8604 } else if (strncmp(message, "1/2", 3) == 0) {
8605 char *p, *q, *r = "";
8606 p = strchr(message, '{');
8615 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8618 } else if (strncmp(message, "White resign", 12) == 0) {
8619 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8621 } else if (strncmp(message, "Black resign", 12) == 0) {
8622 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8624 } else if (strncmp(message, "White matches", 13) == 0 ||
8625 strncmp(message, "Black matches", 13) == 0 ) {
8626 /* [HGM] ignore GNUShogi noises */
8628 } else if (strncmp(message, "White", 5) == 0 &&
8629 message[5] != '(' &&
8630 StrStr(message, "Black") == NULL) {
8631 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8633 } else if (strncmp(message, "Black", 5) == 0 &&
8634 message[5] != '(') {
8635 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8637 } else if (strcmp(message, "resign") == 0 ||
8638 strcmp(message, "computer resigns") == 0) {
8640 case MachinePlaysBlack:
8641 case IcsPlayingBlack:
8642 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8644 case MachinePlaysWhite:
8645 case IcsPlayingWhite:
8646 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8648 case TwoMachinesPlay:
8649 if (cps->twoMachinesColor[0] == 'w')
8650 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8652 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8659 } else if (strncmp(message, "opponent mates", 14) == 0) {
8661 case MachinePlaysBlack:
8662 case IcsPlayingBlack:
8663 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8665 case MachinePlaysWhite:
8666 case IcsPlayingWhite:
8667 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8669 case TwoMachinesPlay:
8670 if (cps->twoMachinesColor[0] == 'w')
8671 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8673 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8680 } else if (strncmp(message, "computer mates", 14) == 0) {
8682 case MachinePlaysBlack:
8683 case IcsPlayingBlack:
8684 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8686 case MachinePlaysWhite:
8687 case IcsPlayingWhite:
8688 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8690 case TwoMachinesPlay:
8691 if (cps->twoMachinesColor[0] == 'w')
8692 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8694 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8701 } else if (strncmp(message, "checkmate", 9) == 0) {
8702 if (WhiteOnMove(forwardMostMove)) {
8703 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8705 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8708 } else if (strstr(message, "Draw") != NULL ||
8709 strstr(message, "game is a draw") != NULL) {
8710 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8712 } else if (strstr(message, "offer") != NULL &&
8713 strstr(message, "draw") != NULL) {
8715 if (appData.zippyPlay && first.initDone) {
8716 /* Relay offer to ICS */
8717 SendToICS(ics_prefix);
8718 SendToICS("draw\n");
8721 cps->offeredDraw = 2; /* valid until this engine moves twice */
8722 if (gameMode == TwoMachinesPlay) {
8723 if (cps->other->offeredDraw) {
8724 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8725 /* [HGM] in two-machine mode we delay relaying draw offer */
8726 /* until after we also have move, to see if it is really claim */
8728 } else if (gameMode == MachinePlaysWhite ||
8729 gameMode == MachinePlaysBlack) {
8730 if (userOfferedDraw) {
8731 DisplayInformation(_("Machine accepts your draw offer"));
8732 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8734 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8741 * Look for thinking output
8743 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8744 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8746 int plylev, mvleft, mvtot, curscore, time;
8747 char mvname[MOVE_LEN];
8751 int prefixHint = FALSE;
8752 mvname[0] = NULLCHAR;
8755 case MachinePlaysBlack:
8756 case IcsPlayingBlack:
8757 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8759 case MachinePlaysWhite:
8760 case IcsPlayingWhite:
8761 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8766 case IcsObserving: /* [DM] icsEngineAnalyze */
8767 if (!appData.icsEngineAnalyze) ignore = TRUE;
8769 case TwoMachinesPlay:
8770 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8780 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8782 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8783 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8785 if (plyext != ' ' && plyext != '\t') {
8789 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8790 if( cps->scoreIsAbsolute &&
8791 ( gameMode == MachinePlaysBlack ||
8792 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8793 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8794 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8795 !WhiteOnMove(currentMove)
8798 curscore = -curscore;
8801 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8803 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8806 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8807 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8808 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8809 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8810 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8811 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8813 } else DisplayError(_("failed writing PV"), 0);
8816 tempStats.depth = plylev;
8817 tempStats.nodes = nodes;
8818 tempStats.time = time;
8819 tempStats.score = curscore;
8820 tempStats.got_only_move = 0;
8822 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8825 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8826 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8827 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8828 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8829 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8830 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8831 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8832 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8835 /* Buffer overflow protection */
8836 if (pv[0] != NULLCHAR) {
8837 if (strlen(pv) >= sizeof(tempStats.movelist)
8838 && appData.debugMode) {
8840 "PV is too long; using the first %u bytes.\n",
8841 (unsigned) sizeof(tempStats.movelist) - 1);
8844 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8846 sprintf(tempStats.movelist, " no PV\n");
8849 if (tempStats.seen_stat) {
8850 tempStats.ok_to_send = 1;
8853 if (strchr(tempStats.movelist, '(') != NULL) {
8854 tempStats.line_is_book = 1;
8855 tempStats.nr_moves = 0;
8856 tempStats.moves_left = 0;
8858 tempStats.line_is_book = 0;
8861 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8862 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8864 SendProgramStatsToFrontend( cps, &tempStats );
8867 [AS] Protect the thinkOutput buffer from overflow... this
8868 is only useful if buf1 hasn't overflowed first!
8870 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8872 (gameMode == TwoMachinesPlay ?
8873 ToUpper(cps->twoMachinesColor[0]) : ' '),
8874 ((double) curscore) / 100.0,
8875 prefixHint ? lastHint : "",
8876 prefixHint ? " " : "" );
8878 if( buf1[0] != NULLCHAR ) {
8879 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8881 if( strlen(pv) > max_len ) {
8882 if( appData.debugMode) {
8883 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8885 pv[max_len+1] = '\0';
8888 strcat( thinkOutput, pv);
8891 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8892 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8893 DisplayMove(currentMove - 1);
8897 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8898 /* crafty (9.25+) says "(only move) <move>"
8899 * if there is only 1 legal move
8901 sscanf(p, "(only move) %s", buf1);
8902 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8903 sprintf(programStats.movelist, "%s (only move)", buf1);
8904 programStats.depth = 1;
8905 programStats.nr_moves = 1;
8906 programStats.moves_left = 1;
8907 programStats.nodes = 1;
8908 programStats.time = 1;
8909 programStats.got_only_move = 1;
8911 /* Not really, but we also use this member to
8912 mean "line isn't going to change" (Crafty
8913 isn't searching, so stats won't change) */
8914 programStats.line_is_book = 1;
8916 SendProgramStatsToFrontend( cps, &programStats );
8918 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8919 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8920 DisplayMove(currentMove - 1);
8923 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8924 &time, &nodes, &plylev, &mvleft,
8925 &mvtot, mvname) >= 5) {
8926 /* The stat01: line is from Crafty (9.29+) in response
8927 to the "." command */
8928 programStats.seen_stat = 1;
8929 cps->maybeThinking = TRUE;
8931 if (programStats.got_only_move || !appData.periodicUpdates)
8934 programStats.depth = plylev;
8935 programStats.time = time;
8936 programStats.nodes = nodes;
8937 programStats.moves_left = mvleft;
8938 programStats.nr_moves = mvtot;
8939 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8940 programStats.ok_to_send = 1;
8941 programStats.movelist[0] = '\0';
8943 SendProgramStatsToFrontend( cps, &programStats );
8947 } else if (strncmp(message,"++",2) == 0) {
8948 /* Crafty 9.29+ outputs this */
8949 programStats.got_fail = 2;
8952 } else if (strncmp(message,"--",2) == 0) {
8953 /* Crafty 9.29+ outputs this */
8954 programStats.got_fail = 1;
8957 } else if (thinkOutput[0] != NULLCHAR &&
8958 strncmp(message, " ", 4) == 0) {
8959 unsigned message_len;
8962 while (*p && *p == ' ') p++;
8964 message_len = strlen( p );
8966 /* [AS] Avoid buffer overflow */
8967 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8968 strcat(thinkOutput, " ");
8969 strcat(thinkOutput, p);
8972 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8973 strcat(programStats.movelist, " ");
8974 strcat(programStats.movelist, p);
8977 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8978 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8979 DisplayMove(currentMove - 1);
8987 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8988 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8990 ChessProgramStats cpstats;
8992 if (plyext != ' ' && plyext != '\t') {
8996 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8997 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8998 curscore = -curscore;
9001 cpstats.depth = plylev;
9002 cpstats.nodes = nodes;
9003 cpstats.time = time;
9004 cpstats.score = curscore;
9005 cpstats.got_only_move = 0;
9006 cpstats.movelist[0] = '\0';
9008 if (buf1[0] != NULLCHAR) {
9009 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9012 cpstats.ok_to_send = 0;
9013 cpstats.line_is_book = 0;
9014 cpstats.nr_moves = 0;
9015 cpstats.moves_left = 0;
9017 SendProgramStatsToFrontend( cps, &cpstats );
9024 /* Parse a game score from the character string "game", and
9025 record it as the history of the current game. The game
9026 score is NOT assumed to start from the standard position.
9027 The display is not updated in any way.
9030 ParseGameHistory (char *game)
9033 int fromX, fromY, toX, toY, boardIndex;
9038 if (appData.debugMode)
9039 fprintf(debugFP, "Parsing game history: %s\n", game);
9041 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9042 gameInfo.site = StrSave(appData.icsHost);
9043 gameInfo.date = PGNDate();
9044 gameInfo.round = StrSave("-");
9046 /* Parse out names of players */
9047 while (*game == ' ') game++;
9049 while (*game != ' ') *p++ = *game++;
9051 gameInfo.white = StrSave(buf);
9052 while (*game == ' ') game++;
9054 while (*game != ' ' && *game != '\n') *p++ = *game++;
9056 gameInfo.black = StrSave(buf);
9059 boardIndex = blackPlaysFirst ? 1 : 0;
9062 yyboardindex = boardIndex;
9063 moveType = (ChessMove) Myylex();
9065 case IllegalMove: /* maybe suicide chess, etc. */
9066 if (appData.debugMode) {
9067 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9068 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9069 setbuf(debugFP, NULL);
9071 case WhitePromotion:
9072 case BlackPromotion:
9073 case WhiteNonPromotion:
9074 case BlackNonPromotion:
9076 case WhiteCapturesEnPassant:
9077 case BlackCapturesEnPassant:
9078 case WhiteKingSideCastle:
9079 case WhiteQueenSideCastle:
9080 case BlackKingSideCastle:
9081 case BlackQueenSideCastle:
9082 case WhiteKingSideCastleWild:
9083 case WhiteQueenSideCastleWild:
9084 case BlackKingSideCastleWild:
9085 case BlackQueenSideCastleWild:
9087 case WhiteHSideCastleFR:
9088 case WhiteASideCastleFR:
9089 case BlackHSideCastleFR:
9090 case BlackASideCastleFR:
9092 fromX = currentMoveString[0] - AAA;
9093 fromY = currentMoveString[1] - ONE;
9094 toX = currentMoveString[2] - AAA;
9095 toY = currentMoveString[3] - ONE;
9096 promoChar = currentMoveString[4];
9100 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9101 fromX = moveType == WhiteDrop ?
9102 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9103 (int) CharToPiece(ToLower(currentMoveString[0]));
9105 toX = currentMoveString[2] - AAA;
9106 toY = currentMoveString[3] - ONE;
9107 promoChar = NULLCHAR;
9111 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9112 if (appData.debugMode) {
9113 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9114 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9115 setbuf(debugFP, NULL);
9117 DisplayError(buf, 0);
9119 case ImpossibleMove:
9121 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9122 if (appData.debugMode) {
9123 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9124 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9125 setbuf(debugFP, NULL);
9127 DisplayError(buf, 0);
9130 if (boardIndex < backwardMostMove) {
9131 /* Oops, gap. How did that happen? */
9132 DisplayError(_("Gap in move list"), 0);
9135 backwardMostMove = blackPlaysFirst ? 1 : 0;
9136 if (boardIndex > forwardMostMove) {
9137 forwardMostMove = boardIndex;
9141 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9142 strcat(parseList[boardIndex-1], " ");
9143 strcat(parseList[boardIndex-1], yy_text);
9155 case GameUnfinished:
9156 if (gameMode == IcsExamining) {
9157 if (boardIndex < backwardMostMove) {
9158 /* Oops, gap. How did that happen? */
9161 backwardMostMove = blackPlaysFirst ? 1 : 0;
9164 gameInfo.result = moveType;
9165 p = strchr(yy_text, '{');
9166 if (p == NULL) p = strchr(yy_text, '(');
9169 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9171 q = strchr(p, *p == '{' ? '}' : ')');
9172 if (q != NULL) *q = NULLCHAR;
9175 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9176 gameInfo.resultDetails = StrSave(p);
9179 if (boardIndex >= forwardMostMove &&
9180 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9181 backwardMostMove = blackPlaysFirst ? 1 : 0;
9184 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9185 fromY, fromX, toY, toX, promoChar,
9186 parseList[boardIndex]);
9187 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9188 /* currentMoveString is set as a side-effect of yylex */
9189 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9190 strcat(moveList[boardIndex], "\n");
9192 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9193 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9199 if(gameInfo.variant != VariantShogi)
9200 strcat(parseList[boardIndex - 1], "+");
9204 strcat(parseList[boardIndex - 1], "#");
9211 /* Apply a move to the given board */
9213 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9215 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9216 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9218 /* [HGM] compute & store e.p. status and castling rights for new position */
9219 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9221 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9222 oldEP = (signed char)board[EP_STATUS];
9223 board[EP_STATUS] = EP_NONE;
9225 if (fromY == DROP_RANK) {
9227 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9228 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9231 piece = board[toY][toX] = (ChessSquare) fromX;
9235 if( board[toY][toX] != EmptySquare )
9236 board[EP_STATUS] = EP_CAPTURE;
9238 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9239 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9240 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9242 if( board[fromY][fromX] == WhitePawn ) {
9243 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9244 board[EP_STATUS] = EP_PAWN_MOVE;
9246 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9247 gameInfo.variant != VariantBerolina || toX < fromX)
9248 board[EP_STATUS] = toX | berolina;
9249 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9250 gameInfo.variant != VariantBerolina || toX > fromX)
9251 board[EP_STATUS] = toX;
9254 if( board[fromY][fromX] == BlackPawn ) {
9255 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9256 board[EP_STATUS] = EP_PAWN_MOVE;
9257 if( toY-fromY== -2) {
9258 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9259 gameInfo.variant != VariantBerolina || toX < fromX)
9260 board[EP_STATUS] = toX | berolina;
9261 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9262 gameInfo.variant != VariantBerolina || toX > fromX)
9263 board[EP_STATUS] = toX;
9267 for(i=0; i<nrCastlingRights; i++) {
9268 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9269 board[CASTLING][i] == toX && castlingRank[i] == toY
9270 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9273 if (fromX == toX && fromY == toY) return;
9275 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9276 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9277 if(gameInfo.variant == VariantKnightmate)
9278 king += (int) WhiteUnicorn - (int) WhiteKing;
9280 /* Code added by Tord: */
9281 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9282 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9283 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9284 board[fromY][fromX] = EmptySquare;
9285 board[toY][toX] = EmptySquare;
9286 if((toX > fromX) != (piece == WhiteRook)) {
9287 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9289 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9291 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9292 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9293 board[fromY][fromX] = EmptySquare;
9294 board[toY][toX] = EmptySquare;
9295 if((toX > fromX) != (piece == BlackRook)) {
9296 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9298 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9300 /* End of code added by Tord */
9302 } else if (board[fromY][fromX] == king
9303 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9304 && toY == fromY && toX > fromX+1) {
9305 board[fromY][fromX] = EmptySquare;
9306 board[toY][toX] = king;
9307 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9308 board[fromY][BOARD_RGHT-1] = EmptySquare;
9309 } else if (board[fromY][fromX] == king
9310 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9311 && toY == fromY && toX < fromX-1) {
9312 board[fromY][fromX] = EmptySquare;
9313 board[toY][toX] = king;
9314 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9315 board[fromY][BOARD_LEFT] = EmptySquare;
9316 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9317 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9318 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9320 /* white pawn promotion */
9321 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9322 if(gameInfo.variant==VariantBughouse ||
9323 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9324 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9325 board[fromY][fromX] = EmptySquare;
9326 } else if ((fromY >= BOARD_HEIGHT>>1)
9328 && gameInfo.variant != VariantXiangqi
9329 && gameInfo.variant != VariantBerolina
9330 && (board[fromY][fromX] == WhitePawn)
9331 && (board[toY][toX] == EmptySquare)) {
9332 board[fromY][fromX] = EmptySquare;
9333 board[toY][toX] = WhitePawn;
9334 captured = board[toY - 1][toX];
9335 board[toY - 1][toX] = EmptySquare;
9336 } else if ((fromY == BOARD_HEIGHT-4)
9338 && gameInfo.variant == VariantBerolina
9339 && (board[fromY][fromX] == WhitePawn)
9340 && (board[toY][toX] == EmptySquare)) {
9341 board[fromY][fromX] = EmptySquare;
9342 board[toY][toX] = WhitePawn;
9343 if(oldEP & EP_BEROLIN_A) {
9344 captured = board[fromY][fromX-1];
9345 board[fromY][fromX-1] = EmptySquare;
9346 }else{ captured = board[fromY][fromX+1];
9347 board[fromY][fromX+1] = EmptySquare;
9349 } else if (board[fromY][fromX] == king
9350 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9351 && toY == fromY && toX > fromX+1) {
9352 board[fromY][fromX] = EmptySquare;
9353 board[toY][toX] = king;
9354 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9355 board[fromY][BOARD_RGHT-1] = EmptySquare;
9356 } else if (board[fromY][fromX] == king
9357 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9358 && toY == fromY && toX < fromX-1) {
9359 board[fromY][fromX] = EmptySquare;
9360 board[toY][toX] = king;
9361 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9362 board[fromY][BOARD_LEFT] = EmptySquare;
9363 } else if (fromY == 7 && fromX == 3
9364 && board[fromY][fromX] == BlackKing
9365 && toY == 7 && toX == 5) {
9366 board[fromY][fromX] = EmptySquare;
9367 board[toY][toX] = BlackKing;
9368 board[fromY][7] = EmptySquare;
9369 board[toY][4] = BlackRook;
9370 } else if (fromY == 7 && fromX == 3
9371 && board[fromY][fromX] == BlackKing
9372 && toY == 7 && toX == 1) {
9373 board[fromY][fromX] = EmptySquare;
9374 board[toY][toX] = BlackKing;
9375 board[fromY][0] = EmptySquare;
9376 board[toY][2] = BlackRook;
9377 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9378 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9379 && toY < promoRank && promoChar
9381 /* black pawn promotion */
9382 board[toY][toX] = CharToPiece(ToLower(promoChar));
9383 if(gameInfo.variant==VariantBughouse ||
9384 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9385 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9386 board[fromY][fromX] = EmptySquare;
9387 } else if ((fromY < BOARD_HEIGHT>>1)
9389 && gameInfo.variant != VariantXiangqi
9390 && gameInfo.variant != VariantBerolina
9391 && (board[fromY][fromX] == BlackPawn)
9392 && (board[toY][toX] == EmptySquare)) {
9393 board[fromY][fromX] = EmptySquare;
9394 board[toY][toX] = BlackPawn;
9395 captured = board[toY + 1][toX];
9396 board[toY + 1][toX] = EmptySquare;
9397 } else if ((fromY == 3)
9399 && gameInfo.variant == VariantBerolina
9400 && (board[fromY][fromX] == BlackPawn)
9401 && (board[toY][toX] == EmptySquare)) {
9402 board[fromY][fromX] = EmptySquare;
9403 board[toY][toX] = BlackPawn;
9404 if(oldEP & EP_BEROLIN_A) {
9405 captured = board[fromY][fromX-1];
9406 board[fromY][fromX-1] = EmptySquare;
9407 }else{ captured = board[fromY][fromX+1];
9408 board[fromY][fromX+1] = EmptySquare;
9411 board[toY][toX] = board[fromY][fromX];
9412 board[fromY][fromX] = EmptySquare;
9416 if (gameInfo.holdingsWidth != 0) {
9418 /* !!A lot more code needs to be written to support holdings */
9419 /* [HGM] OK, so I have written it. Holdings are stored in the */
9420 /* penultimate board files, so they are automaticlly stored */
9421 /* in the game history. */
9422 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9423 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9424 /* Delete from holdings, by decreasing count */
9425 /* and erasing image if necessary */
9426 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9427 if(p < (int) BlackPawn) { /* white drop */
9428 p -= (int)WhitePawn;
9429 p = PieceToNumber((ChessSquare)p);
9430 if(p >= gameInfo.holdingsSize) p = 0;
9431 if(--board[p][BOARD_WIDTH-2] <= 0)
9432 board[p][BOARD_WIDTH-1] = EmptySquare;
9433 if((int)board[p][BOARD_WIDTH-2] < 0)
9434 board[p][BOARD_WIDTH-2] = 0;
9435 } else { /* black drop */
9436 p -= (int)BlackPawn;
9437 p = PieceToNumber((ChessSquare)p);
9438 if(p >= gameInfo.holdingsSize) p = 0;
9439 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9440 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9441 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9442 board[BOARD_HEIGHT-1-p][1] = 0;
9445 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9446 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9447 /* [HGM] holdings: Add to holdings, if holdings exist */
9448 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9449 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9450 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9453 if (p >= (int) BlackPawn) {
9454 p -= (int)BlackPawn;
9455 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9456 /* in Shogi restore piece to its original first */
9457 captured = (ChessSquare) (DEMOTED captured);
9460 p = PieceToNumber((ChessSquare)p);
9461 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9462 board[p][BOARD_WIDTH-2]++;
9463 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9465 p -= (int)WhitePawn;
9466 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9467 captured = (ChessSquare) (DEMOTED captured);
9470 p = PieceToNumber((ChessSquare)p);
9471 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9472 board[BOARD_HEIGHT-1-p][1]++;
9473 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9476 } else if (gameInfo.variant == VariantAtomic) {
9477 if (captured != EmptySquare) {
9479 for (y = toY-1; y <= toY+1; y++) {
9480 for (x = toX-1; x <= toX+1; x++) {
9481 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9482 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9483 board[y][x] = EmptySquare;
9487 board[toY][toX] = EmptySquare;
9490 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9491 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9493 if(promoChar == '+') {
9494 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9495 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9496 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9497 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9498 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9499 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9500 board[toY][toX] = newPiece;
9502 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9503 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9504 // [HGM] superchess: take promotion piece out of holdings
9505 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9506 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9507 if(!--board[k][BOARD_WIDTH-2])
9508 board[k][BOARD_WIDTH-1] = EmptySquare;
9510 if(!--board[BOARD_HEIGHT-1-k][1])
9511 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9517 /* Updates forwardMostMove */
9519 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9521 // forwardMostMove++; // [HGM] bare: moved downstream
9523 (void) CoordsToAlgebraic(boards[forwardMostMove],
9524 PosFlags(forwardMostMove),
9525 fromY, fromX, toY, toX, promoChar,
9526 parseList[forwardMostMove]);
9528 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9529 int timeLeft; static int lastLoadFlag=0; int king, piece;
9530 piece = boards[forwardMostMove][fromY][fromX];
9531 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9532 if(gameInfo.variant == VariantKnightmate)
9533 king += (int) WhiteUnicorn - (int) WhiteKing;
9534 if(forwardMostMove == 0) {
9535 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9536 fprintf(serverMoves, "%s;", UserName());
9537 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9538 fprintf(serverMoves, "%s;", second.tidy);
9539 fprintf(serverMoves, "%s;", first.tidy);
9540 if(gameMode == MachinePlaysWhite)
9541 fprintf(serverMoves, "%s;", UserName());
9542 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9543 fprintf(serverMoves, "%s;", second.tidy);
9544 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9545 lastLoadFlag = loadFlag;
9547 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9548 // print castling suffix
9549 if( toY == fromY && piece == king ) {
9551 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9553 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9556 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9557 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9558 boards[forwardMostMove][toY][toX] == EmptySquare
9559 && fromX != toX && fromY != toY)
9560 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9562 if(promoChar != NULLCHAR)
9563 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9565 char buf[MOVE_LEN*2], *p; int len;
9566 fprintf(serverMoves, "/%d/%d",
9567 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9568 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9569 else timeLeft = blackTimeRemaining/1000;
9570 fprintf(serverMoves, "/%d", timeLeft);
9571 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9572 if(p = strchr(buf, '=')) *p = NULLCHAR;
9573 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9574 fprintf(serverMoves, "/%s", buf);
9576 fflush(serverMoves);
9579 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9580 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9583 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9584 if (commentList[forwardMostMove+1] != NULL) {
9585 free(commentList[forwardMostMove+1]);
9586 commentList[forwardMostMove+1] = NULL;
9588 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9589 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9590 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9591 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9592 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9593 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9594 adjustedClock = FALSE;
9595 gameInfo.result = GameUnfinished;
9596 if (gameInfo.resultDetails != NULL) {
9597 free(gameInfo.resultDetails);
9598 gameInfo.resultDetails = NULL;
9600 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9601 moveList[forwardMostMove - 1]);
9602 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9608 if(gameInfo.variant != VariantShogi)
9609 strcat(parseList[forwardMostMove - 1], "+");
9613 strcat(parseList[forwardMostMove - 1], "#");
9619 /* Updates currentMove if not pausing */
9621 ShowMove (int fromX, int fromY, int toX, int toY)
9623 int instant = (gameMode == PlayFromGameFile) ?
9624 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9625 if(appData.noGUI) return;
9626 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9628 if (forwardMostMove == currentMove + 1) {
9629 AnimateMove(boards[forwardMostMove - 1],
9630 fromX, fromY, toX, toY);
9632 if (appData.highlightLastMove) {
9633 SetHighlights(fromX, fromY, toX, toY);
9636 currentMove = forwardMostMove;
9639 if (instant) return;
9641 DisplayMove(currentMove - 1);
9642 DrawPosition(FALSE, boards[currentMove]);
9643 DisplayBothClocks();
9644 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9648 SendEgtPath (ChessProgramState *cps)
9649 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9650 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9652 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9655 char c, *q = name+1, *r, *s;
9657 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9658 while(*p && *p != ',') *q++ = *p++;
9660 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9661 strcmp(name, ",nalimov:") == 0 ) {
9662 // take nalimov path from the menu-changeable option first, if it is defined
9663 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9664 SendToProgram(buf,cps); // send egtbpath command for nalimov
9666 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9667 (s = StrStr(appData.egtFormats, name)) != NULL) {
9668 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9669 s = r = StrStr(s, ":") + 1; // beginning of path info
9670 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9671 c = *r; *r = 0; // temporarily null-terminate path info
9672 *--q = 0; // strip of trailig ':' from name
9673 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9675 SendToProgram(buf,cps); // send egtbpath command for this format
9677 if(*p == ',') p++; // read away comma to position for next format name
9682 InitChessProgram (ChessProgramState *cps, int setup)
9683 /* setup needed to setup FRC opening position */
9685 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9686 if (appData.noChessProgram) return;
9687 hintRequested = FALSE;
9688 bookRequested = FALSE;
9690 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9691 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9692 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9693 if(cps->memSize) { /* [HGM] memory */
9694 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9695 SendToProgram(buf, cps);
9697 SendEgtPath(cps); /* [HGM] EGT */
9698 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9699 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9700 SendToProgram(buf, cps);
9703 SendToProgram(cps->initString, cps);
9704 if (gameInfo.variant != VariantNormal &&
9705 gameInfo.variant != VariantLoadable
9706 /* [HGM] also send variant if board size non-standard */
9707 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9709 char *v = VariantName(gameInfo.variant);
9710 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9711 /* [HGM] in protocol 1 we have to assume all variants valid */
9712 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9713 DisplayFatalError(buf, 0, 1);
9717 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9718 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9719 if( gameInfo.variant == VariantXiangqi )
9720 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9721 if( gameInfo.variant == VariantShogi )
9722 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9723 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9724 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9725 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9726 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9727 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9728 if( gameInfo.variant == VariantCourier )
9729 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9730 if( gameInfo.variant == VariantSuper )
9731 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9732 if( gameInfo.variant == VariantGreat )
9733 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9734 if( gameInfo.variant == VariantSChess )
9735 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9736 if( gameInfo.variant == VariantGrand )
9737 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9740 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9741 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9742 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9743 if(StrStr(cps->variants, b) == NULL) {
9744 // specific sized variant not known, check if general sizing allowed
9745 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9746 if(StrStr(cps->variants, "boardsize") == NULL) {
9747 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9748 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9749 DisplayFatalError(buf, 0, 1);
9752 /* [HGM] here we really should compare with the maximum supported board size */
9755 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9756 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9757 SendToProgram(buf, cps);
9759 currentlyInitializedVariant = gameInfo.variant;
9761 /* [HGM] send opening position in FRC to first engine */
9763 SendToProgram("force\n", cps);
9765 /* engine is now in force mode! Set flag to wake it up after first move. */
9766 setboardSpoiledMachineBlack = 1;
9770 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9771 SendToProgram(buf, cps);
9773 cps->maybeThinking = FALSE;
9774 cps->offeredDraw = 0;
9775 if (!appData.icsActive) {
9776 SendTimeControl(cps, movesPerSession, timeControl,
9777 timeIncrement, appData.searchDepth,
9780 if (appData.showThinking
9781 // [HGM] thinking: four options require thinking output to be sent
9782 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9784 SendToProgram("post\n", cps);
9786 SendToProgram("hard\n", cps);
9787 if (!appData.ponderNextMove) {
9788 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9789 it without being sure what state we are in first. "hard"
9790 is not a toggle, so that one is OK.
9792 SendToProgram("easy\n", cps);
9795 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9796 SendToProgram(buf, cps);
9798 cps->initDone = TRUE;
9799 ClearEngineOutputPane(cps == &second);
9804 StartChessProgram (ChessProgramState *cps)
9809 if (appData.noChessProgram) return;
9810 cps->initDone = FALSE;
9812 if (strcmp(cps->host, "localhost") == 0) {
9813 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9814 } else if (*appData.remoteShell == NULLCHAR) {
9815 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9817 if (*appData.remoteUser == NULLCHAR) {
9818 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9821 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9822 cps->host, appData.remoteUser, cps->program);
9824 err = StartChildProcess(buf, "", &cps->pr);
9828 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9829 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9830 if(cps != &first) return;
9831 appData.noChessProgram = TRUE;
9834 // DisplayFatalError(buf, err, 1);
9835 // cps->pr = NoProc;
9840 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9841 if (cps->protocolVersion > 1) {
9842 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9843 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9844 cps->comboCnt = 0; // and values of combo boxes
9845 SendToProgram(buf, cps);
9847 SendToProgram("xboard\n", cps);
9852 TwoMachinesEventIfReady P((void))
9854 static int curMess = 0;
9855 if (first.lastPing != first.lastPong) {
9856 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9857 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9860 if (second.lastPing != second.lastPong) {
9861 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9862 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9865 DisplayMessage("", ""); curMess = 0;
9871 MakeName (char *template)
9875 static char buf[MSG_SIZ];
9879 clock = time((time_t *)NULL);
9880 tm = localtime(&clock);
9882 while(*p++ = *template++) if(p[-1] == '%') {
9883 switch(*template++) {
9884 case 0: *p = 0; return buf;
9885 case 'Y': i = tm->tm_year+1900; break;
9886 case 'y': i = tm->tm_year-100; break;
9887 case 'M': i = tm->tm_mon+1; break;
9888 case 'd': i = tm->tm_mday; break;
9889 case 'h': i = tm->tm_hour; break;
9890 case 'm': i = tm->tm_min; break;
9891 case 's': i = tm->tm_sec; break;
9894 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9900 CountPlayers (char *p)
9903 while(p = strchr(p, '\n')) p++, n++; // count participants
9908 WriteTourneyFile (char *results, FILE *f)
9909 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9910 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9911 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9912 // create a file with tournament description
9913 fprintf(f, "-participants {%s}\n", appData.participants);
9914 fprintf(f, "-seedBase %d\n", appData.seedBase);
9915 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9916 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9917 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9918 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9919 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9920 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9921 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9922 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9923 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9924 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9925 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9926 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9928 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9930 fprintf(f, "-mps %d\n", appData.movesPerSession);
9931 fprintf(f, "-tc %s\n", appData.timeControl);
9932 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9934 fprintf(f, "-results \"%s\"\n", results);
9939 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9942 Substitute (char *participants, int expunge)
9944 int i, changed, changes=0, nPlayers=0;
9945 char *p, *q, *r, buf[MSG_SIZ];
9946 if(participants == NULL) return;
9947 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9948 r = p = participants; q = appData.participants;
9949 while(*p && *p == *q) {
9950 if(*p == '\n') r = p+1, nPlayers++;
9953 if(*p) { // difference
9954 while(*p && *p++ != '\n');
9955 while(*q && *q++ != '\n');
9957 changes = 1 + (strcmp(p, q) != 0);
9959 if(changes == 1) { // a single engine mnemonic was changed
9960 q = r; while(*q) nPlayers += (*q++ == '\n');
9961 p = buf; while(*r && (*p = *r++) != '\n') p++;
9963 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9964 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9965 if(mnemonic[i]) { // The substitute is valid
9967 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9968 flock(fileno(f), LOCK_EX);
9969 ParseArgsFromFile(f);
9970 fseek(f, 0, SEEK_SET);
9971 FREE(appData.participants); appData.participants = participants;
9972 if(expunge) { // erase results of replaced engine
9973 int len = strlen(appData.results), w, b, dummy;
9974 for(i=0; i<len; i++) {
9975 Pairing(i, nPlayers, &w, &b, &dummy);
9976 if((w == changed || b == changed) && appData.results[i] == '*') {
9977 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9982 for(i=0; i<len; i++) {
9983 Pairing(i, nPlayers, &w, &b, &dummy);
9984 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9987 WriteTourneyFile(appData.results, f);
9988 fclose(f); // release lock
9991 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9993 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9994 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10000 CreateTourney (char *name)
10003 if(matchMode && strcmp(name, appData.tourneyFile)) {
10004 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10006 if(name[0] == NULLCHAR) {
10007 if(appData.participants[0])
10008 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10011 f = fopen(name, "r");
10012 if(f) { // file exists
10013 ASSIGN(appData.tourneyFile, name);
10014 ParseArgsFromFile(f); // parse it
10016 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10017 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10018 DisplayError(_("Not enough participants"), 0);
10021 ASSIGN(appData.tourneyFile, name);
10022 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10023 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10026 appData.noChessProgram = FALSE;
10027 appData.clockMode = TRUE;
10033 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10035 char buf[MSG_SIZ], *p, *q;
10036 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10037 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10038 skip = !all && group[0]; // if group requested, we start in skip mode
10039 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10040 p = names; q = buf; header = 0;
10041 while(*p && *p != '\n') *q++ = *p++;
10043 if(*p == '\n') p++;
10044 if(buf[0] == '#') {
10045 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10046 depth++; // we must be entering a new group
10047 if(all) continue; // suppress printing group headers when complete list requested
10049 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10051 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10052 if(engineList[i]) free(engineList[i]);
10053 engineList[i] = strdup(buf);
10054 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10055 if(engineMnemonic[i]) free(engineMnemonic[i]);
10056 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10058 sscanf(q + 8, "%s", buf + strlen(buf));
10061 engineMnemonic[i] = strdup(buf);
10064 engineList[i] = engineMnemonic[i] = NULL;
10068 // following implemented as macro to avoid type limitations
10069 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10072 SwapEngines (int n)
10073 { // swap settings for first engine and other engine (so far only some selected options)
10078 SWAP(chessProgram, p)
10080 SWAP(hasOwnBookUCI, h)
10081 SWAP(protocolVersion, h)
10083 SWAP(scoreIsAbsolute, h)
10088 SWAP(engOptions, p)
10089 SWAP(engInitString, p)
10090 SWAP(computerString, p)
10092 SWAP(fenOverride, p)
10094 SWAP(accumulateTC, h)
10099 SetPlayer (int player, char *p)
10100 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10102 char buf[MSG_SIZ], *engineName;
10103 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10104 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10105 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10107 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10108 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10109 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10110 ParseArgsFromString(buf);
10116 char *recentEngines;
10119 RecentEngineEvent (int nr)
10122 // SwapEngines(1); // bump first to second
10123 // ReplaceEngine(&second, 1); // and load it there
10124 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10125 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10126 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10127 ReplaceEngine(&first, 0);
10128 FloatToFront(&appData.recentEngineList, command[n]);
10133 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10134 { // determine players from game number
10135 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10137 if(appData.tourneyType == 0) {
10138 roundsPerCycle = (nPlayers - 1) | 1;
10139 pairingsPerRound = nPlayers / 2;
10140 } else if(appData.tourneyType > 0) {
10141 roundsPerCycle = nPlayers - appData.tourneyType;
10142 pairingsPerRound = appData.tourneyType;
10144 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10145 gamesPerCycle = gamesPerRound * roundsPerCycle;
10146 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10147 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10148 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10149 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10150 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10151 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10153 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10154 if(appData.roundSync) *syncInterval = gamesPerRound;
10156 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10158 if(appData.tourneyType == 0) {
10159 if(curPairing == (nPlayers-1)/2 ) {
10160 *whitePlayer = curRound;
10161 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10163 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10164 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10165 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10166 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10168 } else if(appData.tourneyType > 1) {
10169 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10170 *whitePlayer = curRound + appData.tourneyType;
10171 } else if(appData.tourneyType > 0) {
10172 *whitePlayer = curPairing;
10173 *blackPlayer = curRound + appData.tourneyType;
10176 // take care of white/black alternation per round.
10177 // For cycles and games this is already taken care of by default, derived from matchGame!
10178 return curRound & 1;
10182 NextTourneyGame (int nr, int *swapColors)
10183 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10185 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10187 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10188 tf = fopen(appData.tourneyFile, "r");
10189 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10190 ParseArgsFromFile(tf); fclose(tf);
10191 InitTimeControls(); // TC might be altered from tourney file
10193 nPlayers = CountPlayers(appData.participants); // count participants
10194 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10195 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10198 p = q = appData.results;
10199 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10200 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10201 DisplayMessage(_("Waiting for other game(s)"),"");
10202 waitingForGame = TRUE;
10203 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10206 waitingForGame = FALSE;
10209 if(appData.tourneyType < 0) {
10210 if(nr>=0 && !pairingReceived) {
10212 if(pairing.pr == NoProc) {
10213 if(!appData.pairingEngine[0]) {
10214 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10217 StartChessProgram(&pairing); // starts the pairing engine
10219 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10220 SendToProgram(buf, &pairing);
10221 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10222 SendToProgram(buf, &pairing);
10223 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10225 pairingReceived = 0; // ... so we continue here
10227 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10228 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10229 matchGame = 1; roundNr = nr / syncInterval + 1;
10232 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10234 // redefine engines, engine dir, etc.
10235 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10236 if(first.pr == NoProc) {
10237 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10238 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10240 if(second.pr == NoProc) {
10242 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10243 SwapEngines(1); // and make that valid for second engine by swapping
10244 InitEngine(&second, 1);
10246 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10247 UpdateLogos(FALSE); // leave display to ModeHiglight()
10253 { // performs game initialization that does not invoke engines, and then tries to start the game
10254 int res, firstWhite, swapColors = 0;
10255 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10256 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
10258 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10259 if(strcmp(buf, currentDebugFile)) { // name has changed
10260 FILE *f = fopen(buf, "w");
10261 if(f) { // if opening the new file failed, just keep using the old one
10262 ASSIGN(currentDebugFile, buf);
10266 if(appData.serverFileName) {
10267 if(serverFP) fclose(serverFP);
10268 serverFP = fopen(appData.serverFileName, "w");
10269 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10270 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10274 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10275 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10276 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10277 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10278 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10279 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10280 Reset(FALSE, first.pr != NoProc);
10281 res = LoadGameOrPosition(matchGame); // setup game
10282 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10283 if(!res) return; // abort when bad game/pos file
10284 TwoMachinesEvent();
10288 UserAdjudicationEvent (int result)
10290 ChessMove gameResult = GameIsDrawn;
10293 gameResult = WhiteWins;
10295 else if( result < 0 ) {
10296 gameResult = BlackWins;
10299 if( gameMode == TwoMachinesPlay ) {
10300 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10305 // [HGM] save: calculate checksum of game to make games easily identifiable
10307 StringCheckSum (char *s)
10310 if(s==NULL) return 0;
10311 while(*s) i = i*259 + *s++;
10319 for(i=backwardMostMove; i<forwardMostMove; i++) {
10320 sum += pvInfoList[i].depth;
10321 sum += StringCheckSum(parseList[i]);
10322 sum += StringCheckSum(commentList[i]);
10325 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10326 return sum + StringCheckSum(commentList[i]);
10327 } // end of save patch
10330 GameEnds (ChessMove result, char *resultDetails, int whosays)
10332 GameMode nextGameMode;
10334 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10336 if(endingGame) return; /* [HGM] crash: forbid recursion */
10338 if(twoBoards) { // [HGM] dual: switch back to one board
10339 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10340 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10342 if (appData.debugMode) {
10343 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10344 result, resultDetails ? resultDetails : "(null)", whosays);
10347 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10349 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10350 /* If we are playing on ICS, the server decides when the
10351 game is over, but the engine can offer to draw, claim
10355 if (appData.zippyPlay && first.initDone) {
10356 if (result == GameIsDrawn) {
10357 /* In case draw still needs to be claimed */
10358 SendToICS(ics_prefix);
10359 SendToICS("draw\n");
10360 } else if (StrCaseStr(resultDetails, "resign")) {
10361 SendToICS(ics_prefix);
10362 SendToICS("resign\n");
10366 endingGame = 0; /* [HGM] crash */
10370 /* If we're loading the game from a file, stop */
10371 if (whosays == GE_FILE) {
10372 (void) StopLoadGameTimer();
10376 /* Cancel draw offers */
10377 first.offeredDraw = second.offeredDraw = 0;
10379 /* If this is an ICS game, only ICS can really say it's done;
10380 if not, anyone can. */
10381 isIcsGame = (gameMode == IcsPlayingWhite ||
10382 gameMode == IcsPlayingBlack ||
10383 gameMode == IcsObserving ||
10384 gameMode == IcsExamining);
10386 if (!isIcsGame || whosays == GE_ICS) {
10387 /* OK -- not an ICS game, or ICS said it was done */
10389 if (!isIcsGame && !appData.noChessProgram)
10390 SetUserThinkingEnables();
10392 /* [HGM] if a machine claims the game end we verify this claim */
10393 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10394 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10396 ChessMove trueResult = (ChessMove) -1;
10398 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10399 first.twoMachinesColor[0] :
10400 second.twoMachinesColor[0] ;
10402 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10403 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10404 /* [HGM] verify: engine mate claims accepted if they were flagged */
10405 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10407 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10408 /* [HGM] verify: engine mate claims accepted if they were flagged */
10409 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10411 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10412 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10415 // now verify win claims, but not in drop games, as we don't understand those yet
10416 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10417 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10418 (result == WhiteWins && claimer == 'w' ||
10419 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10420 if (appData.debugMode) {
10421 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10422 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10424 if(result != trueResult) {
10425 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10426 result = claimer == 'w' ? BlackWins : WhiteWins;
10427 resultDetails = buf;
10430 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10431 && (forwardMostMove <= backwardMostMove ||
10432 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10433 (claimer=='b')==(forwardMostMove&1))
10435 /* [HGM] verify: draws that were not flagged are false claims */
10436 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10437 result = claimer == 'w' ? BlackWins : WhiteWins;
10438 resultDetails = buf;
10440 /* (Claiming a loss is accepted no questions asked!) */
10442 /* [HGM] bare: don't allow bare King to win */
10443 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10444 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10445 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10446 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10447 && result != GameIsDrawn)
10448 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10449 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10450 int p = (signed char)boards[forwardMostMove][i][j] - color;
10451 if(p >= 0 && p <= (int)WhiteKing) k++;
10453 if (appData.debugMode) {
10454 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10455 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10458 result = GameIsDrawn;
10459 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10460 resultDetails = buf;
10466 if(serverMoves != NULL && !loadFlag) { char c = '=';
10467 if(result==WhiteWins) c = '+';
10468 if(result==BlackWins) c = '-';
10469 if(resultDetails != NULL)
10470 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10472 if (resultDetails != NULL) {
10473 gameInfo.result = result;
10474 gameInfo.resultDetails = StrSave(resultDetails);
10476 /* display last move only if game was not loaded from file */
10477 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10478 DisplayMove(currentMove - 1);
10480 if (forwardMostMove != 0) {
10481 if (gameMode != PlayFromGameFile && gameMode != EditGame
10482 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10484 if (*appData.saveGameFile != NULLCHAR) {
10485 SaveGameToFile(appData.saveGameFile, TRUE);
10486 } else if (appData.autoSaveGames) {
10489 if (*appData.savePositionFile != NULLCHAR) {
10490 SavePositionToFile(appData.savePositionFile);
10495 /* Tell program how game ended in case it is learning */
10496 /* [HGM] Moved this to after saving the PGN, just in case */
10497 /* engine died and we got here through time loss. In that */
10498 /* case we will get a fatal error writing the pipe, which */
10499 /* would otherwise lose us the PGN. */
10500 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10501 /* output during GameEnds should never be fatal anymore */
10502 if (gameMode == MachinePlaysWhite ||
10503 gameMode == MachinePlaysBlack ||
10504 gameMode == TwoMachinesPlay ||
10505 gameMode == IcsPlayingWhite ||
10506 gameMode == IcsPlayingBlack ||
10507 gameMode == BeginningOfGame) {
10509 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10511 if (first.pr != NoProc) {
10512 SendToProgram(buf, &first);
10514 if (second.pr != NoProc &&
10515 gameMode == TwoMachinesPlay) {
10516 SendToProgram(buf, &second);
10521 if (appData.icsActive) {
10522 if (appData.quietPlay &&
10523 (gameMode == IcsPlayingWhite ||
10524 gameMode == IcsPlayingBlack)) {
10525 SendToICS(ics_prefix);
10526 SendToICS("set shout 1\n");
10528 nextGameMode = IcsIdle;
10529 ics_user_moved = FALSE;
10530 /* clean up premove. It's ugly when the game has ended and the
10531 * premove highlights are still on the board.
10534 gotPremove = FALSE;
10535 ClearPremoveHighlights();
10536 DrawPosition(FALSE, boards[currentMove]);
10538 if (whosays == GE_ICS) {
10541 if (gameMode == IcsPlayingWhite)
10543 else if(gameMode == IcsPlayingBlack)
10544 PlayIcsLossSound();
10547 if (gameMode == IcsPlayingBlack)
10549 else if(gameMode == IcsPlayingWhite)
10550 PlayIcsLossSound();
10553 PlayIcsDrawSound();
10556 PlayIcsUnfinishedSound();
10559 } else if (gameMode == EditGame ||
10560 gameMode == PlayFromGameFile ||
10561 gameMode == AnalyzeMode ||
10562 gameMode == AnalyzeFile) {
10563 nextGameMode = gameMode;
10565 nextGameMode = EndOfGame;
10570 nextGameMode = gameMode;
10573 if (appData.noChessProgram) {
10574 gameMode = nextGameMode;
10576 endingGame = 0; /* [HGM] crash */
10581 /* Put first chess program into idle state */
10582 if (first.pr != NoProc &&
10583 (gameMode == MachinePlaysWhite ||
10584 gameMode == MachinePlaysBlack ||
10585 gameMode == TwoMachinesPlay ||
10586 gameMode == IcsPlayingWhite ||
10587 gameMode == IcsPlayingBlack ||
10588 gameMode == BeginningOfGame)) {
10589 SendToProgram("force\n", &first);
10590 if (first.usePing) {
10592 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10593 SendToProgram(buf, &first);
10596 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10597 /* Kill off first chess program */
10598 if (first.isr != NULL)
10599 RemoveInputSource(first.isr);
10602 if (first.pr != NoProc) {
10604 DoSleep( appData.delayBeforeQuit );
10605 SendToProgram("quit\n", &first);
10606 DoSleep( appData.delayAfterQuit );
10607 DestroyChildProcess(first.pr, first.useSigterm);
10611 if (second.reuse) {
10612 /* Put second chess program into idle state */
10613 if (second.pr != NoProc &&
10614 gameMode == TwoMachinesPlay) {
10615 SendToProgram("force\n", &second);
10616 if (second.usePing) {
10618 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10619 SendToProgram(buf, &second);
10622 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10623 /* Kill off second chess program */
10624 if (second.isr != NULL)
10625 RemoveInputSource(second.isr);
10628 if (second.pr != NoProc) {
10629 DoSleep( appData.delayBeforeQuit );
10630 SendToProgram("quit\n", &second);
10631 DoSleep( appData.delayAfterQuit );
10632 DestroyChildProcess(second.pr, second.useSigterm);
10634 second.pr = NoProc;
10637 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10638 char resChar = '=';
10642 if (first.twoMachinesColor[0] == 'w') {
10645 second.matchWins++;
10650 if (first.twoMachinesColor[0] == 'b') {
10653 second.matchWins++;
10656 case GameUnfinished:
10662 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10663 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10664 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10665 ReserveGame(nextGame, resChar); // sets nextGame
10666 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10667 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10668 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10670 if (nextGame <= appData.matchGames && !abortMatch) {
10671 gameMode = nextGameMode;
10672 matchGame = nextGame; // this will be overruled in tourney mode!
10673 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10674 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10675 endingGame = 0; /* [HGM] crash */
10678 gameMode = nextGameMode;
10679 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10680 first.tidy, second.tidy,
10681 first.matchWins, second.matchWins,
10682 appData.matchGames - (first.matchWins + second.matchWins));
10683 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10684 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10685 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10686 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10687 first.twoMachinesColor = "black\n";
10688 second.twoMachinesColor = "white\n";
10690 first.twoMachinesColor = "white\n";
10691 second.twoMachinesColor = "black\n";
10695 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10696 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10698 gameMode = nextGameMode;
10700 endingGame = 0; /* [HGM] crash */
10701 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10702 if(matchMode == TRUE) { // match through command line: exit with or without popup
10704 ToNrEvent(forwardMostMove);
10705 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10707 } else DisplayFatalError(buf, 0, 0);
10708 } else { // match through menu; just stop, with or without popup
10709 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10712 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10713 } else DisplayNote(buf);
10715 if(ranking) free(ranking);
10719 /* Assumes program was just initialized (initString sent).
10720 Leaves program in force mode. */
10722 FeedMovesToProgram (ChessProgramState *cps, int upto)
10726 if (appData.debugMode)
10727 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10728 startedFromSetupPosition ? "position and " : "",
10729 backwardMostMove, upto, cps->which);
10730 if(currentlyInitializedVariant != gameInfo.variant) {
10732 // [HGM] variantswitch: make engine aware of new variant
10733 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10734 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10735 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10736 SendToProgram(buf, cps);
10737 currentlyInitializedVariant = gameInfo.variant;
10739 SendToProgram("force\n", cps);
10740 if (startedFromSetupPosition) {
10741 SendBoard(cps, backwardMostMove);
10742 if (appData.debugMode) {
10743 fprintf(debugFP, "feedMoves\n");
10746 for (i = backwardMostMove; i < upto; i++) {
10747 SendMoveToProgram(i, cps);
10753 ResurrectChessProgram ()
10755 /* The chess program may have exited.
10756 If so, restart it and feed it all the moves made so far. */
10757 static int doInit = 0;
10759 if (appData.noChessProgram) return 1;
10761 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10762 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10763 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10764 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10766 if (first.pr != NoProc) return 1;
10767 StartChessProgram(&first);
10769 InitChessProgram(&first, FALSE);
10770 FeedMovesToProgram(&first, currentMove);
10772 if (!first.sendTime) {
10773 /* can't tell gnuchess what its clock should read,
10774 so we bow to its notion. */
10776 timeRemaining[0][currentMove] = whiteTimeRemaining;
10777 timeRemaining[1][currentMove] = blackTimeRemaining;
10780 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10781 appData.icsEngineAnalyze) && first.analysisSupport) {
10782 SendToProgram("analyze\n", &first);
10783 first.analyzing = TRUE;
10789 * Button procedures
10792 Reset (int redraw, int init)
10796 if (appData.debugMode) {
10797 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10798 redraw, init, gameMode);
10800 CleanupTail(); // [HGM] vari: delete any stored variations
10801 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10802 pausing = pauseExamInvalid = FALSE;
10803 startedFromSetupPosition = blackPlaysFirst = FALSE;
10805 whiteFlag = blackFlag = FALSE;
10806 userOfferedDraw = FALSE;
10807 hintRequested = bookRequested = FALSE;
10808 first.maybeThinking = FALSE;
10809 second.maybeThinking = FALSE;
10810 first.bookSuspend = FALSE; // [HGM] book
10811 second.bookSuspend = FALSE;
10812 thinkOutput[0] = NULLCHAR;
10813 lastHint[0] = NULLCHAR;
10814 ClearGameInfo(&gameInfo);
10815 gameInfo.variant = StringToVariant(appData.variant);
10816 ics_user_moved = ics_clock_paused = FALSE;
10817 ics_getting_history = H_FALSE;
10819 white_holding[0] = black_holding[0] = NULLCHAR;
10820 ClearProgramStats();
10821 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10825 flipView = appData.flipView;
10826 ClearPremoveHighlights();
10827 gotPremove = FALSE;
10828 alarmSounded = FALSE;
10830 GameEnds(EndOfFile, NULL, GE_PLAYER);
10831 if(appData.serverMovesName != NULL) {
10832 /* [HGM] prepare to make moves file for broadcasting */
10833 clock_t t = clock();
10834 if(serverMoves != NULL) fclose(serverMoves);
10835 serverMoves = fopen(appData.serverMovesName, "r");
10836 if(serverMoves != NULL) {
10837 fclose(serverMoves);
10838 /* delay 15 sec before overwriting, so all clients can see end */
10839 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10841 serverMoves = fopen(appData.serverMovesName, "w");
10845 gameMode = BeginningOfGame;
10847 if(appData.icsActive) gameInfo.variant = VariantNormal;
10848 currentMove = forwardMostMove = backwardMostMove = 0;
10849 MarkTargetSquares(1);
10850 InitPosition(redraw);
10851 for (i = 0; i < MAX_MOVES; i++) {
10852 if (commentList[i] != NULL) {
10853 free(commentList[i]);
10854 commentList[i] = NULL;
10858 timeRemaining[0][0] = whiteTimeRemaining;
10859 timeRemaining[1][0] = blackTimeRemaining;
10861 if (first.pr == NoProc) {
10862 StartChessProgram(&first);
10865 InitChessProgram(&first, startedFromSetupPosition);
10868 DisplayMessage("", "");
10869 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10870 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10871 ClearMap(); // [HGM] exclude: invalidate map
10875 AutoPlayGameLoop ()
10878 if (!AutoPlayOneMove())
10880 if (matchMode || appData.timeDelay == 0)
10882 if (appData.timeDelay < 0)
10884 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10893 int fromX, fromY, toX, toY;
10895 if (appData.debugMode) {
10896 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10899 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10902 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10903 pvInfoList[currentMove].depth = programStats.depth;
10904 pvInfoList[currentMove].score = programStats.score;
10905 pvInfoList[currentMove].time = 0;
10906 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10909 if (currentMove >= forwardMostMove) {
10910 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10911 // gameMode = EndOfGame;
10912 // ModeHighlight();
10914 /* [AS] Clear current move marker at the end of a game */
10915 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10920 toX = moveList[currentMove][2] - AAA;
10921 toY = moveList[currentMove][3] - ONE;
10923 if (moveList[currentMove][1] == '@') {
10924 if (appData.highlightLastMove) {
10925 SetHighlights(-1, -1, toX, toY);
10928 fromX = moveList[currentMove][0] - AAA;
10929 fromY = moveList[currentMove][1] - ONE;
10931 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10933 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10935 if (appData.highlightLastMove) {
10936 SetHighlights(fromX, fromY, toX, toY);
10939 DisplayMove(currentMove);
10940 SendMoveToProgram(currentMove++, &first);
10941 DisplayBothClocks();
10942 DrawPosition(FALSE, boards[currentMove]);
10943 // [HGM] PV info: always display, routine tests if empty
10944 DisplayComment(currentMove - 1, commentList[currentMove]);
10950 LoadGameOneMove (ChessMove readAhead)
10952 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10953 char promoChar = NULLCHAR;
10954 ChessMove moveType;
10955 char move[MSG_SIZ];
10958 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10959 gameMode != AnalyzeMode && gameMode != Training) {
10964 yyboardindex = forwardMostMove;
10965 if (readAhead != EndOfFile) {
10966 moveType = readAhead;
10968 if (gameFileFP == NULL)
10970 moveType = (ChessMove) Myylex();
10974 switch (moveType) {
10976 if (appData.debugMode)
10977 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10980 /* append the comment but don't display it */
10981 AppendComment(currentMove, p, FALSE);
10984 case WhiteCapturesEnPassant:
10985 case BlackCapturesEnPassant:
10986 case WhitePromotion:
10987 case BlackPromotion:
10988 case WhiteNonPromotion:
10989 case BlackNonPromotion:
10991 case WhiteKingSideCastle:
10992 case WhiteQueenSideCastle:
10993 case BlackKingSideCastle:
10994 case BlackQueenSideCastle:
10995 case WhiteKingSideCastleWild:
10996 case WhiteQueenSideCastleWild:
10997 case BlackKingSideCastleWild:
10998 case BlackQueenSideCastleWild:
11000 case WhiteHSideCastleFR:
11001 case WhiteASideCastleFR:
11002 case BlackHSideCastleFR:
11003 case BlackASideCastleFR:
11005 if (appData.debugMode)
11006 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11007 fromX = currentMoveString[0] - AAA;
11008 fromY = currentMoveString[1] - ONE;
11009 toX = currentMoveString[2] - AAA;
11010 toY = currentMoveString[3] - ONE;
11011 promoChar = currentMoveString[4];
11016 if (appData.debugMode)
11017 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11018 fromX = moveType == WhiteDrop ?
11019 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11020 (int) CharToPiece(ToLower(currentMoveString[0]));
11022 toX = currentMoveString[2] - AAA;
11023 toY = currentMoveString[3] - ONE;
11029 case GameUnfinished:
11030 if (appData.debugMode)
11031 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11032 p = strchr(yy_text, '{');
11033 if (p == NULL) p = strchr(yy_text, '(');
11036 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11038 q = strchr(p, *p == '{' ? '}' : ')');
11039 if (q != NULL) *q = NULLCHAR;
11042 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11043 GameEnds(moveType, p, GE_FILE);
11045 if (cmailMsgLoaded) {
11047 flipView = WhiteOnMove(currentMove);
11048 if (moveType == GameUnfinished) flipView = !flipView;
11049 if (appData.debugMode)
11050 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11055 if (appData.debugMode)
11056 fprintf(debugFP, "Parser hit end of file\n");
11057 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11063 if (WhiteOnMove(currentMove)) {
11064 GameEnds(BlackWins, "Black mates", GE_FILE);
11066 GameEnds(WhiteWins, "White mates", GE_FILE);
11070 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11076 case MoveNumberOne:
11077 if (lastLoadGameStart == GNUChessGame) {
11078 /* GNUChessGames have numbers, but they aren't move numbers */
11079 if (appData.debugMode)
11080 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11081 yy_text, (int) moveType);
11082 return LoadGameOneMove(EndOfFile); /* tail recursion */
11084 /* else fall thru */
11089 /* Reached start of next game in file */
11090 if (appData.debugMode)
11091 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11092 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11098 if (WhiteOnMove(currentMove)) {
11099 GameEnds(BlackWins, "Black mates", GE_FILE);
11101 GameEnds(WhiteWins, "White mates", GE_FILE);
11105 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11111 case PositionDiagram: /* should not happen; ignore */
11112 case ElapsedTime: /* ignore */
11113 case NAG: /* ignore */
11114 if (appData.debugMode)
11115 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11116 yy_text, (int) moveType);
11117 return LoadGameOneMove(EndOfFile); /* tail recursion */
11120 if (appData.testLegality) {
11121 if (appData.debugMode)
11122 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11123 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11124 (forwardMostMove / 2) + 1,
11125 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11126 DisplayError(move, 0);
11129 if (appData.debugMode)
11130 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11131 yy_text, currentMoveString);
11132 fromX = currentMoveString[0] - AAA;
11133 fromY = currentMoveString[1] - ONE;
11134 toX = currentMoveString[2] - AAA;
11135 toY = currentMoveString[3] - ONE;
11136 promoChar = currentMoveString[4];
11140 case AmbiguousMove:
11141 if (appData.debugMode)
11142 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11143 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11144 (forwardMostMove / 2) + 1,
11145 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11146 DisplayError(move, 0);
11151 case ImpossibleMove:
11152 if (appData.debugMode)
11153 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11154 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11155 (forwardMostMove / 2) + 1,
11156 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11157 DisplayError(move, 0);
11163 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11164 DrawPosition(FALSE, boards[currentMove]);
11165 DisplayBothClocks();
11166 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11167 DisplayComment(currentMove - 1, commentList[currentMove]);
11169 (void) StopLoadGameTimer();
11171 cmailOldMove = forwardMostMove;
11174 /* currentMoveString is set as a side-effect of yylex */
11176 thinkOutput[0] = NULLCHAR;
11177 MakeMove(fromX, fromY, toX, toY, promoChar);
11178 currentMove = forwardMostMove;
11183 /* Load the nth game from the given file */
11185 LoadGameFromFile (char *filename, int n, char *title, int useList)
11190 if (strcmp(filename, "-") == 0) {
11194 f = fopen(filename, "rb");
11196 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11197 DisplayError(buf, errno);
11201 if (fseek(f, 0, 0) == -1) {
11202 /* f is not seekable; probably a pipe */
11205 if (useList && n == 0) {
11206 int error = GameListBuild(f);
11208 DisplayError(_("Cannot build game list"), error);
11209 } else if (!ListEmpty(&gameList) &&
11210 ((ListGame *) gameList.tailPred)->number > 1) {
11211 GameListPopUp(f, title);
11218 return LoadGame(f, n, title, FALSE);
11223 MakeRegisteredMove ()
11225 int fromX, fromY, toX, toY;
11227 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11228 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11231 if (appData.debugMode)
11232 fprintf(debugFP, "Restoring %s for game %d\n",
11233 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11235 thinkOutput[0] = NULLCHAR;
11236 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11237 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11238 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11239 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11240 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11241 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11242 MakeMove(fromX, fromY, toX, toY, promoChar);
11243 ShowMove(fromX, fromY, toX, toY);
11245 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11252 if (WhiteOnMove(currentMove)) {
11253 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11255 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11260 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11267 if (WhiteOnMove(currentMove)) {
11268 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11270 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11275 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11286 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11288 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11292 if (gameNumber > nCmailGames) {
11293 DisplayError(_("No more games in this message"), 0);
11296 if (f == lastLoadGameFP) {
11297 int offset = gameNumber - lastLoadGameNumber;
11299 cmailMsg[0] = NULLCHAR;
11300 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11301 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11302 nCmailMovesRegistered--;
11304 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11305 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11306 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11309 if (! RegisterMove()) return FALSE;
11313 retVal = LoadGame(f, gameNumber, title, useList);
11315 /* Make move registered during previous look at this game, if any */
11316 MakeRegisteredMove();
11318 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11319 commentList[currentMove]
11320 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11321 DisplayComment(currentMove - 1, commentList[currentMove]);
11327 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11329 ReloadGame (int offset)
11331 int gameNumber = lastLoadGameNumber + offset;
11332 if (lastLoadGameFP == NULL) {
11333 DisplayError(_("No game has been loaded yet"), 0);
11336 if (gameNumber <= 0) {
11337 DisplayError(_("Can't back up any further"), 0);
11340 if (cmailMsgLoaded) {
11341 return CmailLoadGame(lastLoadGameFP, gameNumber,
11342 lastLoadGameTitle, lastLoadGameUseList);
11344 return LoadGame(lastLoadGameFP, gameNumber,
11345 lastLoadGameTitle, lastLoadGameUseList);
11349 int keys[EmptySquare+1];
11352 PositionMatches (Board b1, Board b2)
11355 switch(appData.searchMode) {
11356 case 1: return CompareWithRights(b1, b2);
11358 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11359 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11363 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11364 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11365 sum += keys[b1[r][f]] - keys[b2[r][f]];
11369 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11370 sum += keys[b1[r][f]] - keys[b2[r][f]];
11382 int pieceList[256], quickBoard[256];
11383 ChessSquare pieceType[256] = { EmptySquare };
11384 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11385 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11386 int soughtTotal, turn;
11387 Boolean epOK, flipSearch;
11390 unsigned char piece, to;
11393 #define DSIZE (250000)
11395 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11396 Move *moveDatabase = initialSpace;
11397 unsigned int movePtr, dataSize = DSIZE;
11400 MakePieceList (Board board, int *counts)
11402 int r, f, n=Q_PROMO, total=0;
11403 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11404 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11405 int sq = f + (r<<4);
11406 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11407 quickBoard[sq] = ++n;
11409 pieceType[n] = board[r][f];
11410 counts[board[r][f]]++;
11411 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11412 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11416 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11421 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11423 int sq = fromX + (fromY<<4);
11424 int piece = quickBoard[sq];
11425 quickBoard[sq] = 0;
11426 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11427 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11428 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11429 moveDatabase[movePtr++].piece = Q_WCASTL;
11430 quickBoard[sq] = piece;
11431 piece = quickBoard[from]; quickBoard[from] = 0;
11432 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11434 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11435 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11436 moveDatabase[movePtr++].piece = Q_BCASTL;
11437 quickBoard[sq] = piece;
11438 piece = quickBoard[from]; quickBoard[from] = 0;
11439 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11441 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11442 quickBoard[(fromY<<4)+toX] = 0;
11443 moveDatabase[movePtr].piece = Q_EP;
11444 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11445 moveDatabase[movePtr].to = sq;
11447 if(promoPiece != pieceType[piece]) {
11448 moveDatabase[movePtr++].piece = Q_PROMO;
11449 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11451 moveDatabase[movePtr].piece = piece;
11452 quickBoard[sq] = piece;
11457 PackGame (Board board)
11459 Move *newSpace = NULL;
11460 moveDatabase[movePtr].piece = 0; // terminate previous game
11461 if(movePtr > dataSize) {
11462 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11463 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11464 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11467 Move *p = moveDatabase, *q = newSpace;
11468 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11469 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11470 moveDatabase = newSpace;
11471 } else { // calloc failed, we must be out of memory. Too bad...
11472 dataSize = 0; // prevent calloc events for all subsequent games
11473 return 0; // and signal this one isn't cached
11477 MakePieceList(board, counts);
11482 QuickCompare (Board board, int *minCounts, int *maxCounts)
11483 { // compare according to search mode
11485 switch(appData.searchMode)
11487 case 1: // exact position match
11488 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11489 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11490 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11493 case 2: // can have extra material on empty squares
11494 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11495 if(board[r][f] == EmptySquare) continue;
11496 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11499 case 3: // material with exact Pawn structure
11500 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11501 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11502 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11503 } // fall through to material comparison
11504 case 4: // exact material
11505 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11507 case 6: // material range with given imbalance
11508 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11509 // fall through to range comparison
11510 case 5: // material range
11511 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11517 QuickScan (Board board, Move *move)
11518 { // reconstruct game,and compare all positions in it
11519 int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11521 int piece = move->piece;
11522 int to = move->to, from = pieceList[piece];
11523 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11524 if(!piece) return -1;
11525 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11526 piece = (++move)->piece;
11527 from = pieceList[piece];
11528 counts[pieceType[piece]]--;
11529 pieceType[piece] = (ChessSquare) move->to;
11530 counts[move->to]++;
11531 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11532 counts[pieceType[quickBoard[to]]]--;
11533 quickBoard[to] = 0; total--;
11536 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11538 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11539 from = pieceList[piece]; // so this must be King
11540 quickBoard[from] = 0;
11541 pieceList[piece] = to;
11542 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11543 quickBoard[from] = 0; // rook
11544 quickBoard[to] = piece;
11545 to = move->to; piece = move->piece;
11549 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11550 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11551 quickBoard[from] = 0;
11553 quickBoard[to] = piece;
11554 pieceList[piece] = to;
11556 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11557 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11558 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11559 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11561 static int lastCounts[EmptySquare+1];
11563 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11564 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11565 } else stretch = 0;
11566 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11567 move++; delayedKing = -1;
11575 flipSearch = FALSE;
11576 CopyBoard(soughtBoard, boards[currentMove]);
11577 soughtTotal = MakePieceList(soughtBoard, maxSought);
11578 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11579 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11580 CopyBoard(reverseBoard, boards[currentMove]);
11581 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11582 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11583 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11584 reverseBoard[r][f] = piece;
11586 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11587 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11588 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11589 || (boards[currentMove][CASTLING][2] == NoRights ||
11590 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11591 && (boards[currentMove][CASTLING][5] == NoRights ||
11592 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11595 CopyBoard(flipBoard, soughtBoard);
11596 CopyBoard(rotateBoard, reverseBoard);
11597 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11598 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11599 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11602 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11603 if(appData.searchMode >= 5) {
11604 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11605 MakePieceList(soughtBoard, minSought);
11606 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11608 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11609 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11612 GameInfo dummyInfo;
11615 GameContainsPosition (FILE *f, ListGame *lg)
11617 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11618 int fromX, fromY, toX, toY;
11620 static int initDone=FALSE;
11622 // weed out games based on numerical tag comparison
11623 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11624 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11625 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11626 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11628 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11631 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11632 else CopyBoard(boards[scratch], initialPosition); // default start position
11635 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11636 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11639 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11640 fseek(f, lg->offset, 0);
11643 yyboardindex = scratch;
11644 quickFlag = plyNr+1;
11649 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11655 if(plyNr) return -1; // after we have seen moves, this is for new game
11658 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11659 case ImpossibleMove:
11660 case WhiteWins: // game ends here with these four
11663 case GameUnfinished:
11667 if(appData.testLegality) return -1;
11668 case WhiteCapturesEnPassant:
11669 case BlackCapturesEnPassant:
11670 case WhitePromotion:
11671 case BlackPromotion:
11672 case WhiteNonPromotion:
11673 case BlackNonPromotion:
11675 case WhiteKingSideCastle:
11676 case WhiteQueenSideCastle:
11677 case BlackKingSideCastle:
11678 case BlackQueenSideCastle:
11679 case WhiteKingSideCastleWild:
11680 case WhiteQueenSideCastleWild:
11681 case BlackKingSideCastleWild:
11682 case BlackQueenSideCastleWild:
11683 case WhiteHSideCastleFR:
11684 case WhiteASideCastleFR:
11685 case BlackHSideCastleFR:
11686 case BlackASideCastleFR:
11687 fromX = currentMoveString[0] - AAA;
11688 fromY = currentMoveString[1] - ONE;
11689 toX = currentMoveString[2] - AAA;
11690 toY = currentMoveString[3] - ONE;
11691 promoChar = currentMoveString[4];
11695 fromX = next == WhiteDrop ?
11696 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11697 (int) CharToPiece(ToLower(currentMoveString[0]));
11699 toX = currentMoveString[2] - AAA;
11700 toY = currentMoveString[3] - ONE;
11704 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11706 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11707 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11708 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11709 if(appData.findMirror) {
11710 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11711 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11716 /* Load the nth game from open file f */
11718 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11722 int gn = gameNumber;
11723 ListGame *lg = NULL;
11724 int numPGNTags = 0;
11726 GameMode oldGameMode;
11727 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11729 if (appData.debugMode)
11730 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11732 if (gameMode == Training )
11733 SetTrainingModeOff();
11735 oldGameMode = gameMode;
11736 if (gameMode != BeginningOfGame) {
11737 Reset(FALSE, TRUE);
11741 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11742 fclose(lastLoadGameFP);
11746 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11749 fseek(f, lg->offset, 0);
11750 GameListHighlight(gameNumber);
11751 pos = lg->position;
11755 DisplayError(_("Game number out of range"), 0);
11760 if (fseek(f, 0, 0) == -1) {
11761 if (f == lastLoadGameFP ?
11762 gameNumber == lastLoadGameNumber + 1 :
11766 DisplayError(_("Can't seek on game file"), 0);
11771 lastLoadGameFP = f;
11772 lastLoadGameNumber = gameNumber;
11773 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11774 lastLoadGameUseList = useList;
11778 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11779 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11780 lg->gameInfo.black);
11782 } else if (*title != NULLCHAR) {
11783 if (gameNumber > 1) {
11784 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11787 DisplayTitle(title);
11791 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11792 gameMode = PlayFromGameFile;
11796 currentMove = forwardMostMove = backwardMostMove = 0;
11797 CopyBoard(boards[0], initialPosition);
11801 * Skip the first gn-1 games in the file.
11802 * Also skip over anything that precedes an identifiable
11803 * start of game marker, to avoid being confused by
11804 * garbage at the start of the file. Currently
11805 * recognized start of game markers are the move number "1",
11806 * the pattern "gnuchess .* game", the pattern
11807 * "^[#;%] [^ ]* game file", and a PGN tag block.
11808 * A game that starts with one of the latter two patterns
11809 * will also have a move number 1, possibly
11810 * following a position diagram.
11811 * 5-4-02: Let's try being more lenient and allowing a game to
11812 * start with an unnumbered move. Does that break anything?
11814 cm = lastLoadGameStart = EndOfFile;
11816 yyboardindex = forwardMostMove;
11817 cm = (ChessMove) Myylex();
11820 if (cmailMsgLoaded) {
11821 nCmailGames = CMAIL_MAX_GAMES - gn;
11824 DisplayError(_("Game not found in file"), 0);
11831 lastLoadGameStart = cm;
11834 case MoveNumberOne:
11835 switch (lastLoadGameStart) {
11840 case MoveNumberOne:
11842 gn--; /* count this game */
11843 lastLoadGameStart = cm;
11852 switch (lastLoadGameStart) {
11855 case MoveNumberOne:
11857 gn--; /* count this game */
11858 lastLoadGameStart = cm;
11861 lastLoadGameStart = cm; /* game counted already */
11869 yyboardindex = forwardMostMove;
11870 cm = (ChessMove) Myylex();
11871 } while (cm == PGNTag || cm == Comment);
11878 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11879 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11880 != CMAIL_OLD_RESULT) {
11882 cmailResult[ CMAIL_MAX_GAMES
11883 - gn - 1] = CMAIL_OLD_RESULT;
11889 /* Only a NormalMove can be at the start of a game
11890 * without a position diagram. */
11891 if (lastLoadGameStart == EndOfFile ) {
11893 lastLoadGameStart = MoveNumberOne;
11902 if (appData.debugMode)
11903 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11905 if (cm == XBoardGame) {
11906 /* Skip any header junk before position diagram and/or move 1 */
11908 yyboardindex = forwardMostMove;
11909 cm = (ChessMove) Myylex();
11911 if (cm == EndOfFile ||
11912 cm == GNUChessGame || cm == XBoardGame) {
11913 /* Empty game; pretend end-of-file and handle later */
11918 if (cm == MoveNumberOne || cm == PositionDiagram ||
11919 cm == PGNTag || cm == Comment)
11922 } else if (cm == GNUChessGame) {
11923 if (gameInfo.event != NULL) {
11924 free(gameInfo.event);
11926 gameInfo.event = StrSave(yy_text);
11929 startedFromSetupPosition = FALSE;
11930 while (cm == PGNTag) {
11931 if (appData.debugMode)
11932 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11933 err = ParsePGNTag(yy_text, &gameInfo);
11934 if (!err) numPGNTags++;
11936 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11937 if(gameInfo.variant != oldVariant) {
11938 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11939 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11940 InitPosition(TRUE);
11941 oldVariant = gameInfo.variant;
11942 if (appData.debugMode)
11943 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11947 if (gameInfo.fen != NULL) {
11948 Board initial_position;
11949 startedFromSetupPosition = TRUE;
11950 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11952 DisplayError(_("Bad FEN position in file"), 0);
11955 CopyBoard(boards[0], initial_position);
11956 if (blackPlaysFirst) {
11957 currentMove = forwardMostMove = backwardMostMove = 1;
11958 CopyBoard(boards[1], initial_position);
11959 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11960 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11961 timeRemaining[0][1] = whiteTimeRemaining;
11962 timeRemaining[1][1] = blackTimeRemaining;
11963 if (commentList[0] != NULL) {
11964 commentList[1] = commentList[0];
11965 commentList[0] = NULL;
11968 currentMove = forwardMostMove = backwardMostMove = 0;
11970 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11972 initialRulePlies = FENrulePlies;
11973 for( i=0; i< nrCastlingRights; i++ )
11974 initialRights[i] = initial_position[CASTLING][i];
11976 yyboardindex = forwardMostMove;
11977 free(gameInfo.fen);
11978 gameInfo.fen = NULL;
11981 yyboardindex = forwardMostMove;
11982 cm = (ChessMove) Myylex();
11984 /* Handle comments interspersed among the tags */
11985 while (cm == Comment) {
11987 if (appData.debugMode)
11988 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11990 AppendComment(currentMove, p, FALSE);
11991 yyboardindex = forwardMostMove;
11992 cm = (ChessMove) Myylex();
11996 /* don't rely on existence of Event tag since if game was
11997 * pasted from clipboard the Event tag may not exist
11999 if (numPGNTags > 0){
12001 if (gameInfo.variant == VariantNormal) {
12002 VariantClass v = StringToVariant(gameInfo.event);
12003 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12004 if(v < VariantShogi) gameInfo.variant = v;
12007 if( appData.autoDisplayTags ) {
12008 tags = PGNTags(&gameInfo);
12009 TagsPopUp(tags, CmailMsg());
12014 /* Make something up, but don't display it now */
12019 if (cm == PositionDiagram) {
12022 Board initial_position;
12024 if (appData.debugMode)
12025 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12027 if (!startedFromSetupPosition) {
12029 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12030 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12041 initial_position[i][j++] = CharToPiece(*p);
12044 while (*p == ' ' || *p == '\t' ||
12045 *p == '\n' || *p == '\r') p++;
12047 if (strncmp(p, "black", strlen("black"))==0)
12048 blackPlaysFirst = TRUE;
12050 blackPlaysFirst = FALSE;
12051 startedFromSetupPosition = TRUE;
12053 CopyBoard(boards[0], initial_position);
12054 if (blackPlaysFirst) {
12055 currentMove = forwardMostMove = backwardMostMove = 1;
12056 CopyBoard(boards[1], initial_position);
12057 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12058 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12059 timeRemaining[0][1] = whiteTimeRemaining;
12060 timeRemaining[1][1] = blackTimeRemaining;
12061 if (commentList[0] != NULL) {
12062 commentList[1] = commentList[0];
12063 commentList[0] = NULL;
12066 currentMove = forwardMostMove = backwardMostMove = 0;
12069 yyboardindex = forwardMostMove;
12070 cm = (ChessMove) Myylex();
12073 if (first.pr == NoProc) {
12074 StartChessProgram(&first);
12076 InitChessProgram(&first, FALSE);
12077 SendToProgram("force\n", &first);
12078 if (startedFromSetupPosition) {
12079 SendBoard(&first, forwardMostMove);
12080 if (appData.debugMode) {
12081 fprintf(debugFP, "Load Game\n");
12083 DisplayBothClocks();
12086 /* [HGM] server: flag to write setup moves in broadcast file as one */
12087 loadFlag = appData.suppressLoadMoves;
12089 while (cm == Comment) {
12091 if (appData.debugMode)
12092 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12094 AppendComment(currentMove, p, FALSE);
12095 yyboardindex = forwardMostMove;
12096 cm = (ChessMove) Myylex();
12099 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12100 cm == WhiteWins || cm == BlackWins ||
12101 cm == GameIsDrawn || cm == GameUnfinished) {
12102 DisplayMessage("", _("No moves in game"));
12103 if (cmailMsgLoaded) {
12104 if (appData.debugMode)
12105 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12109 DrawPosition(FALSE, boards[currentMove]);
12110 DisplayBothClocks();
12111 gameMode = EditGame;
12118 // [HGM] PV info: routine tests if comment empty
12119 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12120 DisplayComment(currentMove - 1, commentList[currentMove]);
12122 if (!matchMode && appData.timeDelay != 0)
12123 DrawPosition(FALSE, boards[currentMove]);
12125 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12126 programStats.ok_to_send = 1;
12129 /* if the first token after the PGN tags is a move
12130 * and not move number 1, retrieve it from the parser
12132 if (cm != MoveNumberOne)
12133 LoadGameOneMove(cm);
12135 /* load the remaining moves from the file */
12136 while (LoadGameOneMove(EndOfFile)) {
12137 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12138 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12141 /* rewind to the start of the game */
12142 currentMove = backwardMostMove;
12144 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12146 if (oldGameMode == AnalyzeFile ||
12147 oldGameMode == AnalyzeMode) {
12148 AnalyzeFileEvent();
12151 if (!matchMode && pos >= 0) {
12152 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12154 if (matchMode || appData.timeDelay == 0) {
12156 } else if (appData.timeDelay > 0) {
12157 AutoPlayGameLoop();
12160 if (appData.debugMode)
12161 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12163 loadFlag = 0; /* [HGM] true game starts */
12167 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12169 ReloadPosition (int offset)
12171 int positionNumber = lastLoadPositionNumber + offset;
12172 if (lastLoadPositionFP == NULL) {
12173 DisplayError(_("No position has been loaded yet"), 0);
12176 if (positionNumber <= 0) {
12177 DisplayError(_("Can't back up any further"), 0);
12180 return LoadPosition(lastLoadPositionFP, positionNumber,
12181 lastLoadPositionTitle);
12184 /* Load the nth position from the given file */
12186 LoadPositionFromFile (char *filename, int n, char *title)
12191 if (strcmp(filename, "-") == 0) {
12192 return LoadPosition(stdin, n, "stdin");
12194 f = fopen(filename, "rb");
12196 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12197 DisplayError(buf, errno);
12200 return LoadPosition(f, n, title);
12205 /* Load the nth position from the given open file, and close it */
12207 LoadPosition (FILE *f, int positionNumber, char *title)
12209 char *p, line[MSG_SIZ];
12210 Board initial_position;
12211 int i, j, fenMode, pn;
12213 if (gameMode == Training )
12214 SetTrainingModeOff();
12216 if (gameMode != BeginningOfGame) {
12217 Reset(FALSE, TRUE);
12219 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12220 fclose(lastLoadPositionFP);
12222 if (positionNumber == 0) positionNumber = 1;
12223 lastLoadPositionFP = f;
12224 lastLoadPositionNumber = positionNumber;
12225 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12226 if (first.pr == NoProc && !appData.noChessProgram) {
12227 StartChessProgram(&first);
12228 InitChessProgram(&first, FALSE);
12230 pn = positionNumber;
12231 if (positionNumber < 0) {
12232 /* Negative position number means to seek to that byte offset */
12233 if (fseek(f, -positionNumber, 0) == -1) {
12234 DisplayError(_("Can't seek on position file"), 0);
12239 if (fseek(f, 0, 0) == -1) {
12240 if (f == lastLoadPositionFP ?
12241 positionNumber == lastLoadPositionNumber + 1 :
12242 positionNumber == 1) {
12245 DisplayError(_("Can't seek on position file"), 0);
12250 /* See if this file is FEN or old-style xboard */
12251 if (fgets(line, MSG_SIZ, f) == NULL) {
12252 DisplayError(_("Position not found in file"), 0);
12255 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12256 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12259 if (fenMode || line[0] == '#') pn--;
12261 /* skip positions before number pn */
12262 if (fgets(line, MSG_SIZ, f) == NULL) {
12264 DisplayError(_("Position not found in file"), 0);
12267 if (fenMode || line[0] == '#') pn--;
12272 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12273 DisplayError(_("Bad FEN position in file"), 0);
12277 (void) fgets(line, MSG_SIZ, f);
12278 (void) fgets(line, MSG_SIZ, f);
12280 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12281 (void) fgets(line, MSG_SIZ, f);
12282 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12285 initial_position[i][j++] = CharToPiece(*p);
12289 blackPlaysFirst = FALSE;
12291 (void) fgets(line, MSG_SIZ, f);
12292 if (strncmp(line, "black", strlen("black"))==0)
12293 blackPlaysFirst = TRUE;
12296 startedFromSetupPosition = TRUE;
12298 CopyBoard(boards[0], initial_position);
12299 if (blackPlaysFirst) {
12300 currentMove = forwardMostMove = backwardMostMove = 1;
12301 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12302 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12303 CopyBoard(boards[1], initial_position);
12304 DisplayMessage("", _("Black to play"));
12306 currentMove = forwardMostMove = backwardMostMove = 0;
12307 DisplayMessage("", _("White to play"));
12309 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12310 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12311 SendToProgram("force\n", &first);
12312 SendBoard(&first, forwardMostMove);
12314 if (appData.debugMode) {
12316 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12317 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12318 fprintf(debugFP, "Load Position\n");
12321 if (positionNumber > 1) {
12322 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12323 DisplayTitle(line);
12325 DisplayTitle(title);
12327 gameMode = EditGame;
12330 timeRemaining[0][1] = whiteTimeRemaining;
12331 timeRemaining[1][1] = blackTimeRemaining;
12332 DrawPosition(FALSE, boards[currentMove]);
12339 CopyPlayerNameIntoFileName (char **dest, char *src)
12341 while (*src != NULLCHAR && *src != ',') {
12346 *(*dest)++ = *src++;
12352 DefaultFileName (char *ext)
12354 static char def[MSG_SIZ];
12357 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12359 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12361 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12363 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12370 /* Save the current game to the given file */
12372 SaveGameToFile (char *filename, int append)
12376 int result, i, t,tot=0;
12378 if (strcmp(filename, "-") == 0) {
12379 return SaveGame(stdout, 0, NULL);
12381 for(i=0; i<10; i++) { // upto 10 tries
12382 f = fopen(filename, append ? "a" : "w");
12383 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12384 if(f || errno != 13) break;
12385 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12389 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12390 DisplayError(buf, errno);
12393 safeStrCpy(buf, lastMsg, MSG_SIZ);
12394 DisplayMessage(_("Waiting for access to save file"), "");
12395 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12396 DisplayMessage(_("Saving game"), "");
12397 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12398 result = SaveGame(f, 0, NULL);
12399 DisplayMessage(buf, "");
12406 SavePart (char *str)
12408 static char buf[MSG_SIZ];
12411 p = strchr(str, ' ');
12412 if (p == NULL) return str;
12413 strncpy(buf, str, p - str);
12414 buf[p - str] = NULLCHAR;
12418 #define PGN_MAX_LINE 75
12420 #define PGN_SIDE_WHITE 0
12421 #define PGN_SIDE_BLACK 1
12424 FindFirstMoveOutOfBook (int side)
12428 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12429 int index = backwardMostMove;
12430 int has_book_hit = 0;
12432 if( (index % 2) != side ) {
12436 while( index < forwardMostMove ) {
12437 /* Check to see if engine is in book */
12438 int depth = pvInfoList[index].depth;
12439 int score = pvInfoList[index].score;
12445 else if( score == 0 && depth == 63 ) {
12446 in_book = 1; /* Zappa */
12448 else if( score == 2 && depth == 99 ) {
12449 in_book = 1; /* Abrok */
12452 has_book_hit += in_book;
12468 GetOutOfBookInfo (char * buf)
12472 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12474 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12475 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12479 if( oob[0] >= 0 || oob[1] >= 0 ) {
12480 for( i=0; i<2; i++ ) {
12484 if( i > 0 && oob[0] >= 0 ) {
12485 strcat( buf, " " );
12488 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12489 sprintf( buf+strlen(buf), "%s%.2f",
12490 pvInfoList[idx].score >= 0 ? "+" : "",
12491 pvInfoList[idx].score / 100.0 );
12497 /* Save game in PGN style and close the file */
12499 SaveGamePGN (FILE *f)
12501 int i, offset, linelen, newblock;
12505 int movelen, numlen, blank;
12506 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12508 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12510 tm = time((time_t *) NULL);
12512 PrintPGNTags(f, &gameInfo);
12514 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12516 if (backwardMostMove > 0 || startedFromSetupPosition) {
12517 char *fen = PositionToFEN(backwardMostMove, NULL);
12518 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12519 fprintf(f, "\n{--------------\n");
12520 PrintPosition(f, backwardMostMove);
12521 fprintf(f, "--------------}\n");
12525 /* [AS] Out of book annotation */
12526 if( appData.saveOutOfBookInfo ) {
12529 GetOutOfBookInfo( buf );
12531 if( buf[0] != '\0' ) {
12532 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12539 i = backwardMostMove;
12543 while (i < forwardMostMove) {
12544 /* Print comments preceding this move */
12545 if (commentList[i] != NULL) {
12546 if (linelen > 0) fprintf(f, "\n");
12547 fprintf(f, "%s", commentList[i]);
12552 /* Format move number */
12554 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12557 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12559 numtext[0] = NULLCHAR;
12561 numlen = strlen(numtext);
12564 /* Print move number */
12565 blank = linelen > 0 && numlen > 0;
12566 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12575 fprintf(f, "%s", numtext);
12579 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12580 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12583 blank = linelen > 0 && movelen > 0;
12584 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12593 fprintf(f, "%s", move_buffer);
12594 linelen += movelen;
12596 /* [AS] Add PV info if present */
12597 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12598 /* [HGM] add time */
12599 char buf[MSG_SIZ]; int seconds;
12601 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12607 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12610 seconds = (seconds + 4)/10; // round to full seconds
12612 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12614 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12617 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12618 pvInfoList[i].score >= 0 ? "+" : "",
12619 pvInfoList[i].score / 100.0,
12620 pvInfoList[i].depth,
12623 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12625 /* Print score/depth */
12626 blank = linelen > 0 && movelen > 0;
12627 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12636 fprintf(f, "%s", move_buffer);
12637 linelen += movelen;
12643 /* Start a new line */
12644 if (linelen > 0) fprintf(f, "\n");
12646 /* Print comments after last move */
12647 if (commentList[i] != NULL) {
12648 fprintf(f, "%s\n", commentList[i]);
12652 if (gameInfo.resultDetails != NULL &&
12653 gameInfo.resultDetails[0] != NULLCHAR) {
12654 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12655 PGNResult(gameInfo.result));
12657 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12661 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12665 /* Save game in old style and close the file */
12667 SaveGameOldStyle (FILE *f)
12672 tm = time((time_t *) NULL);
12674 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12677 if (backwardMostMove > 0 || startedFromSetupPosition) {
12678 fprintf(f, "\n[--------------\n");
12679 PrintPosition(f, backwardMostMove);
12680 fprintf(f, "--------------]\n");
12685 i = backwardMostMove;
12686 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12688 while (i < forwardMostMove) {
12689 if (commentList[i] != NULL) {
12690 fprintf(f, "[%s]\n", commentList[i]);
12693 if ((i % 2) == 1) {
12694 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12697 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12699 if (commentList[i] != NULL) {
12703 if (i >= forwardMostMove) {
12707 fprintf(f, "%s\n", parseList[i]);
12712 if (commentList[i] != NULL) {
12713 fprintf(f, "[%s]\n", commentList[i]);
12716 /* This isn't really the old style, but it's close enough */
12717 if (gameInfo.resultDetails != NULL &&
12718 gameInfo.resultDetails[0] != NULLCHAR) {
12719 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12720 gameInfo.resultDetails);
12722 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12729 /* Save the current game to open file f and close the file */
12731 SaveGame (FILE *f, int dummy, char *dummy2)
12733 if (gameMode == EditPosition) EditPositionDone(TRUE);
12734 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12735 if (appData.oldSaveStyle)
12736 return SaveGameOldStyle(f);
12738 return SaveGamePGN(f);
12741 /* Save the current position to the given file */
12743 SavePositionToFile (char *filename)
12748 if (strcmp(filename, "-") == 0) {
12749 return SavePosition(stdout, 0, NULL);
12751 f = fopen(filename, "a");
12753 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12754 DisplayError(buf, errno);
12757 safeStrCpy(buf, lastMsg, MSG_SIZ);
12758 DisplayMessage(_("Waiting for access to save file"), "");
12759 flock(fileno(f), LOCK_EX); // [HGM] lock
12760 DisplayMessage(_("Saving position"), "");
12761 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12762 SavePosition(f, 0, NULL);
12763 DisplayMessage(buf, "");
12769 /* Save the current position to the given open file and close the file */
12771 SavePosition (FILE *f, int dummy, char *dummy2)
12776 if (gameMode == EditPosition) EditPositionDone(TRUE);
12777 if (appData.oldSaveStyle) {
12778 tm = time((time_t *) NULL);
12780 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12782 fprintf(f, "[--------------\n");
12783 PrintPosition(f, currentMove);
12784 fprintf(f, "--------------]\n");
12786 fen = PositionToFEN(currentMove, NULL);
12787 fprintf(f, "%s\n", fen);
12795 ReloadCmailMsgEvent (int unregister)
12798 static char *inFilename = NULL;
12799 static char *outFilename;
12801 struct stat inbuf, outbuf;
12804 /* Any registered moves are unregistered if unregister is set, */
12805 /* i.e. invoked by the signal handler */
12807 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12808 cmailMoveRegistered[i] = FALSE;
12809 if (cmailCommentList[i] != NULL) {
12810 free(cmailCommentList[i]);
12811 cmailCommentList[i] = NULL;
12814 nCmailMovesRegistered = 0;
12817 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12818 cmailResult[i] = CMAIL_NOT_RESULT;
12822 if (inFilename == NULL) {
12823 /* Because the filenames are static they only get malloced once */
12824 /* and they never get freed */
12825 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12826 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12828 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12829 sprintf(outFilename, "%s.out", appData.cmailGameName);
12832 status = stat(outFilename, &outbuf);
12834 cmailMailedMove = FALSE;
12836 status = stat(inFilename, &inbuf);
12837 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12840 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12841 counts the games, notes how each one terminated, etc.
12843 It would be nice to remove this kludge and instead gather all
12844 the information while building the game list. (And to keep it
12845 in the game list nodes instead of having a bunch of fixed-size
12846 parallel arrays.) Note this will require getting each game's
12847 termination from the PGN tags, as the game list builder does
12848 not process the game moves. --mann
12850 cmailMsgLoaded = TRUE;
12851 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12853 /* Load first game in the file or popup game menu */
12854 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12856 #endif /* !WIN32 */
12864 char string[MSG_SIZ];
12866 if ( cmailMailedMove
12867 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12868 return TRUE; /* Allow free viewing */
12871 /* Unregister move to ensure that we don't leave RegisterMove */
12872 /* with the move registered when the conditions for registering no */
12874 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12875 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12876 nCmailMovesRegistered --;
12878 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12880 free(cmailCommentList[lastLoadGameNumber - 1]);
12881 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12885 if (cmailOldMove == -1) {
12886 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12890 if (currentMove > cmailOldMove + 1) {
12891 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12895 if (currentMove < cmailOldMove) {
12896 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12900 if (forwardMostMove > currentMove) {
12901 /* Silently truncate extra moves */
12905 if ( (currentMove == cmailOldMove + 1)
12906 || ( (currentMove == cmailOldMove)
12907 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12908 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12909 if (gameInfo.result != GameUnfinished) {
12910 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12913 if (commentList[currentMove] != NULL) {
12914 cmailCommentList[lastLoadGameNumber - 1]
12915 = StrSave(commentList[currentMove]);
12917 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12919 if (appData.debugMode)
12920 fprintf(debugFP, "Saving %s for game %d\n",
12921 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12923 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12925 f = fopen(string, "w");
12926 if (appData.oldSaveStyle) {
12927 SaveGameOldStyle(f); /* also closes the file */
12929 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12930 f = fopen(string, "w");
12931 SavePosition(f, 0, NULL); /* also closes the file */
12933 fprintf(f, "{--------------\n");
12934 PrintPosition(f, currentMove);
12935 fprintf(f, "--------------}\n\n");
12937 SaveGame(f, 0, NULL); /* also closes the file*/
12940 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12941 nCmailMovesRegistered ++;
12942 } else if (nCmailGames == 1) {
12943 DisplayError(_("You have not made a move yet"), 0);
12954 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12955 FILE *commandOutput;
12956 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12957 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12963 if (! cmailMsgLoaded) {
12964 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12968 if (nCmailGames == nCmailResults) {
12969 DisplayError(_("No unfinished games"), 0);
12973 #if CMAIL_PROHIBIT_REMAIL
12974 if (cmailMailedMove) {
12975 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);
12976 DisplayError(msg, 0);
12981 if (! (cmailMailedMove || RegisterMove())) return;
12983 if ( cmailMailedMove
12984 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12985 snprintf(string, MSG_SIZ, partCommandString,
12986 appData.debugMode ? " -v" : "", appData.cmailGameName);
12987 commandOutput = popen(string, "r");
12989 if (commandOutput == NULL) {
12990 DisplayError(_("Failed to invoke cmail"), 0);
12992 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12993 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12995 if (nBuffers > 1) {
12996 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12997 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12998 nBytes = MSG_SIZ - 1;
13000 (void) memcpy(msg, buffer, nBytes);
13002 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13004 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13005 cmailMailedMove = TRUE; /* Prevent >1 moves */
13008 for (i = 0; i < nCmailGames; i ++) {
13009 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13014 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13016 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13018 appData.cmailGameName,
13020 LoadGameFromFile(buffer, 1, buffer, FALSE);
13021 cmailMsgLoaded = FALSE;
13025 DisplayInformation(msg);
13026 pclose(commandOutput);
13029 if ((*cmailMsg) != '\0') {
13030 DisplayInformation(cmailMsg);
13035 #endif /* !WIN32 */
13044 int prependComma = 0;
13046 char string[MSG_SIZ]; /* Space for game-list */
13049 if (!cmailMsgLoaded) return "";
13051 if (cmailMailedMove) {
13052 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13054 /* Create a list of games left */
13055 snprintf(string, MSG_SIZ, "[");
13056 for (i = 0; i < nCmailGames; i ++) {
13057 if (! ( cmailMoveRegistered[i]
13058 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13059 if (prependComma) {
13060 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13062 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13066 strcat(string, number);
13069 strcat(string, "]");
13071 if (nCmailMovesRegistered + nCmailResults == 0) {
13072 switch (nCmailGames) {
13074 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13078 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13082 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13087 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13089 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13094 if (nCmailResults == nCmailGames) {
13095 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13097 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13102 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13114 if (gameMode == Training)
13115 SetTrainingModeOff();
13118 cmailMsgLoaded = FALSE;
13119 if (appData.icsActive) {
13120 SendToICS(ics_prefix);
13121 SendToICS("refresh\n");
13126 ExitEvent (int status)
13130 /* Give up on clean exit */
13134 /* Keep trying for clean exit */
13138 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13140 if (telnetISR != NULL) {
13141 RemoveInputSource(telnetISR);
13143 if (icsPR != NoProc) {
13144 DestroyChildProcess(icsPR, TRUE);
13147 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13148 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13150 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13151 /* make sure this other one finishes before killing it! */
13152 if(endingGame) { int count = 0;
13153 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13154 while(endingGame && count++ < 10) DoSleep(1);
13155 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13158 /* Kill off chess programs */
13159 if (first.pr != NoProc) {
13162 DoSleep( appData.delayBeforeQuit );
13163 SendToProgram("quit\n", &first);
13164 DoSleep( appData.delayAfterQuit );
13165 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13167 if (second.pr != NoProc) {
13168 DoSleep( appData.delayBeforeQuit );
13169 SendToProgram("quit\n", &second);
13170 DoSleep( appData.delayAfterQuit );
13171 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13173 if (first.isr != NULL) {
13174 RemoveInputSource(first.isr);
13176 if (second.isr != NULL) {
13177 RemoveInputSource(second.isr);
13180 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13181 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13183 ShutDownFrontEnd();
13190 if (appData.debugMode)
13191 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13195 if (gameMode == MachinePlaysWhite ||
13196 gameMode == MachinePlaysBlack) {
13199 DisplayBothClocks();
13201 if (gameMode == PlayFromGameFile) {
13202 if (appData.timeDelay >= 0)
13203 AutoPlayGameLoop();
13204 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13205 Reset(FALSE, TRUE);
13206 SendToICS(ics_prefix);
13207 SendToICS("refresh\n");
13208 } else if (currentMove < forwardMostMove) {
13209 ForwardInner(forwardMostMove);
13211 pauseExamInvalid = FALSE;
13213 switch (gameMode) {
13217 pauseExamForwardMostMove = forwardMostMove;
13218 pauseExamInvalid = FALSE;
13221 case IcsPlayingWhite:
13222 case IcsPlayingBlack:
13226 case PlayFromGameFile:
13227 (void) StopLoadGameTimer();
13231 case BeginningOfGame:
13232 if (appData.icsActive) return;
13233 /* else fall through */
13234 case MachinePlaysWhite:
13235 case MachinePlaysBlack:
13236 case TwoMachinesPlay:
13237 if (forwardMostMove == 0)
13238 return; /* don't pause if no one has moved */
13239 if ((gameMode == MachinePlaysWhite &&
13240 !WhiteOnMove(forwardMostMove)) ||
13241 (gameMode == MachinePlaysBlack &&
13242 WhiteOnMove(forwardMostMove))) {
13253 EditCommentEvent ()
13255 char title[MSG_SIZ];
13257 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13258 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13260 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13261 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13262 parseList[currentMove - 1]);
13265 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13272 char *tags = PGNTags(&gameInfo);
13274 EditTagsPopUp(tags, NULL);
13279 AnalyzeModeEvent ()
13281 if (appData.noChessProgram || gameMode == AnalyzeMode)
13284 if (gameMode != AnalyzeFile) {
13285 if (!appData.icsEngineAnalyze) {
13287 if (gameMode != EditGame) return;
13289 ResurrectChessProgram();
13290 SendToProgram("analyze\n", &first);
13291 first.analyzing = TRUE;
13292 /*first.maybeThinking = TRUE;*/
13293 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13294 EngineOutputPopUp();
13296 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13301 StartAnalysisClock();
13302 GetTimeMark(&lastNodeCountTime);
13307 AnalyzeFileEvent ()
13309 if (appData.noChessProgram || gameMode == AnalyzeFile)
13312 if (gameMode != AnalyzeMode) {
13314 if (gameMode != EditGame) return;
13315 ResurrectChessProgram();
13316 SendToProgram("analyze\n", &first);
13317 first.analyzing = TRUE;
13318 /*first.maybeThinking = TRUE;*/
13319 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13320 EngineOutputPopUp();
13322 gameMode = AnalyzeFile;
13327 StartAnalysisClock();
13328 GetTimeMark(&lastNodeCountTime);
13330 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13334 MachineWhiteEvent ()
13337 char *bookHit = NULL;
13339 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13343 if (gameMode == PlayFromGameFile ||
13344 gameMode == TwoMachinesPlay ||
13345 gameMode == Training ||
13346 gameMode == AnalyzeMode ||
13347 gameMode == EndOfGame)
13350 if (gameMode == EditPosition)
13351 EditPositionDone(TRUE);
13353 if (!WhiteOnMove(currentMove)) {
13354 DisplayError(_("It is not White's turn"), 0);
13358 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13361 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13362 gameMode == AnalyzeFile)
13365 ResurrectChessProgram(); /* in case it isn't running */
13366 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13367 gameMode = MachinePlaysWhite;
13370 gameMode = MachinePlaysWhite;
13374 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13376 if (first.sendName) {
13377 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13378 SendToProgram(buf, &first);
13380 if (first.sendTime) {
13381 if (first.useColors) {
13382 SendToProgram("black\n", &first); /*gnu kludge*/
13384 SendTimeRemaining(&first, TRUE);
13386 if (first.useColors) {
13387 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13389 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13390 SetMachineThinkingEnables();
13391 first.maybeThinking = TRUE;
13395 if (appData.autoFlipView && !flipView) {
13396 flipView = !flipView;
13397 DrawPosition(FALSE, NULL);
13398 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13401 if(bookHit) { // [HGM] book: simulate book reply
13402 static char bookMove[MSG_SIZ]; // a bit generous?
13404 programStats.nodes = programStats.depth = programStats.time =
13405 programStats.score = programStats.got_only_move = 0;
13406 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13408 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13409 strcat(bookMove, bookHit);
13410 HandleMachineMove(bookMove, &first);
13415 MachineBlackEvent ()
13418 char *bookHit = NULL;
13420 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13424 if (gameMode == PlayFromGameFile ||
13425 gameMode == TwoMachinesPlay ||
13426 gameMode == Training ||
13427 gameMode == AnalyzeMode ||
13428 gameMode == EndOfGame)
13431 if (gameMode == EditPosition)
13432 EditPositionDone(TRUE);
13434 if (WhiteOnMove(currentMove)) {
13435 DisplayError(_("It is not Black's turn"), 0);
13439 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13442 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13443 gameMode == AnalyzeFile)
13446 ResurrectChessProgram(); /* in case it isn't running */
13447 gameMode = MachinePlaysBlack;
13451 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13453 if (first.sendName) {
13454 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13455 SendToProgram(buf, &first);
13457 if (first.sendTime) {
13458 if (first.useColors) {
13459 SendToProgram("white\n", &first); /*gnu kludge*/
13461 SendTimeRemaining(&first, FALSE);
13463 if (first.useColors) {
13464 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13466 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13467 SetMachineThinkingEnables();
13468 first.maybeThinking = TRUE;
13471 if (appData.autoFlipView && flipView) {
13472 flipView = !flipView;
13473 DrawPosition(FALSE, NULL);
13474 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13476 if(bookHit) { // [HGM] book: simulate book reply
13477 static char bookMove[MSG_SIZ]; // a bit generous?
13479 programStats.nodes = programStats.depth = programStats.time =
13480 programStats.score = programStats.got_only_move = 0;
13481 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13483 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13484 strcat(bookMove, bookHit);
13485 HandleMachineMove(bookMove, &first);
13491 DisplayTwoMachinesTitle ()
13494 if (appData.matchGames > 0) {
13495 if(appData.tourneyFile[0]) {
13496 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13497 gameInfo.white, _("vs."), gameInfo.black,
13498 nextGame+1, appData.matchGames+1,
13499 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13501 if (first.twoMachinesColor[0] == 'w') {
13502 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13503 gameInfo.white, _("vs."), gameInfo.black,
13504 first.matchWins, second.matchWins,
13505 matchGame - 1 - (first.matchWins + second.matchWins));
13507 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13508 gameInfo.white, _("vs."), gameInfo.black,
13509 second.matchWins, first.matchWins,
13510 matchGame - 1 - (first.matchWins + second.matchWins));
13513 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13519 SettingsMenuIfReady ()
13521 if (second.lastPing != second.lastPong) {
13522 DisplayMessage("", _("Waiting for second chess program"));
13523 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13527 DisplayMessage("", "");
13528 SettingsPopUp(&second);
13532 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13535 if (cps->pr == NoProc) {
13536 StartChessProgram(cps);
13537 if (cps->protocolVersion == 1) {
13540 /* kludge: allow timeout for initial "feature" command */
13542 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13543 DisplayMessage("", buf);
13544 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13552 TwoMachinesEvent P((void))
13556 ChessProgramState *onmove;
13557 char *bookHit = NULL;
13558 static int stalling = 0;
13562 if (appData.noChessProgram) return;
13564 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13565 DisplayError("second engine does not play this", 0);
13569 switch (gameMode) {
13570 case TwoMachinesPlay:
13572 case MachinePlaysWhite:
13573 case MachinePlaysBlack:
13574 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13575 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13579 case BeginningOfGame:
13580 case PlayFromGameFile:
13583 if (gameMode != EditGame) return;
13586 EditPositionDone(TRUE);
13597 // forwardMostMove = currentMove;
13598 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13600 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13602 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13603 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13604 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13608 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13609 SendToProgram("force\n", &second);
13611 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13614 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13615 if(appData.matchPause>10000 || appData.matchPause<10)
13616 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13617 wait = SubtractTimeMarks(&now, &pauseStart);
13618 if(wait < appData.matchPause) {
13619 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13622 // we are now committed to starting the game
13624 DisplayMessage("", "");
13625 if (startedFromSetupPosition) {
13626 SendBoard(&second, backwardMostMove);
13627 if (appData.debugMode) {
13628 fprintf(debugFP, "Two Machines\n");
13631 for (i = backwardMostMove; i < forwardMostMove; i++) {
13632 SendMoveToProgram(i, &second);
13635 gameMode = TwoMachinesPlay;
13637 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13639 DisplayTwoMachinesTitle();
13641 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13646 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13647 SendToProgram(first.computerString, &first);
13648 if (first.sendName) {
13649 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13650 SendToProgram(buf, &first);
13652 SendToProgram(second.computerString, &second);
13653 if (second.sendName) {
13654 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13655 SendToProgram(buf, &second);
13659 if (!first.sendTime || !second.sendTime) {
13660 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13661 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13663 if (onmove->sendTime) {
13664 if (onmove->useColors) {
13665 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13667 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13669 if (onmove->useColors) {
13670 SendToProgram(onmove->twoMachinesColor, onmove);
13672 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13673 // SendToProgram("go\n", onmove);
13674 onmove->maybeThinking = TRUE;
13675 SetMachineThinkingEnables();
13679 if(bookHit) { // [HGM] book: simulate book reply
13680 static char bookMove[MSG_SIZ]; // a bit generous?
13682 programStats.nodes = programStats.depth = programStats.time =
13683 programStats.score = programStats.got_only_move = 0;
13684 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13686 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13687 strcat(bookMove, bookHit);
13688 savedMessage = bookMove; // args for deferred call
13689 savedState = onmove;
13690 ScheduleDelayedEvent(DeferredBookMove, 1);
13697 if (gameMode == Training) {
13698 SetTrainingModeOff();
13699 gameMode = PlayFromGameFile;
13700 DisplayMessage("", _("Training mode off"));
13702 gameMode = Training;
13703 animateTraining = appData.animate;
13705 /* make sure we are not already at the end of the game */
13706 if (currentMove < forwardMostMove) {
13707 SetTrainingModeOn();
13708 DisplayMessage("", _("Training mode on"));
13710 gameMode = PlayFromGameFile;
13711 DisplayError(_("Already at end of game"), 0);
13720 if (!appData.icsActive) return;
13721 switch (gameMode) {
13722 case IcsPlayingWhite:
13723 case IcsPlayingBlack:
13726 case BeginningOfGame:
13734 EditPositionDone(TRUE);
13747 gameMode = IcsIdle;
13757 switch (gameMode) {
13759 SetTrainingModeOff();
13761 case MachinePlaysWhite:
13762 case MachinePlaysBlack:
13763 case BeginningOfGame:
13764 SendToProgram("force\n", &first);
13765 SetUserThinkingEnables();
13767 case PlayFromGameFile:
13768 (void) StopLoadGameTimer();
13769 if (gameFileFP != NULL) {
13774 EditPositionDone(TRUE);
13779 SendToProgram("force\n", &first);
13781 case TwoMachinesPlay:
13782 GameEnds(EndOfFile, NULL, GE_PLAYER);
13783 ResurrectChessProgram();
13784 SetUserThinkingEnables();
13787 ResurrectChessProgram();
13789 case IcsPlayingBlack:
13790 case IcsPlayingWhite:
13791 DisplayError(_("Warning: You are still playing a game"), 0);
13794 DisplayError(_("Warning: You are still observing a game"), 0);
13797 DisplayError(_("Warning: You are still examining a game"), 0);
13808 first.offeredDraw = second.offeredDraw = 0;
13810 if (gameMode == PlayFromGameFile) {
13811 whiteTimeRemaining = timeRemaining[0][currentMove];
13812 blackTimeRemaining = timeRemaining[1][currentMove];
13816 if (gameMode == MachinePlaysWhite ||
13817 gameMode == MachinePlaysBlack ||
13818 gameMode == TwoMachinesPlay ||
13819 gameMode == EndOfGame) {
13820 i = forwardMostMove;
13821 while (i > currentMove) {
13822 SendToProgram("undo\n", &first);
13825 if(!adjustedClock) {
13826 whiteTimeRemaining = timeRemaining[0][currentMove];
13827 blackTimeRemaining = timeRemaining[1][currentMove];
13828 DisplayBothClocks();
13830 if (whiteFlag || blackFlag) {
13831 whiteFlag = blackFlag = 0;
13836 gameMode = EditGame;
13843 EditPositionEvent ()
13845 if (gameMode == EditPosition) {
13851 if (gameMode != EditGame) return;
13853 gameMode = EditPosition;
13856 if (currentMove > 0)
13857 CopyBoard(boards[0], boards[currentMove]);
13859 blackPlaysFirst = !WhiteOnMove(currentMove);
13861 currentMove = forwardMostMove = backwardMostMove = 0;
13862 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13864 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13870 /* [DM] icsEngineAnalyze - possible call from other functions */
13871 if (appData.icsEngineAnalyze) {
13872 appData.icsEngineAnalyze = FALSE;
13874 DisplayMessage("",_("Close ICS engine analyze..."));
13876 if (first.analysisSupport && first.analyzing) {
13877 SendToProgram("exit\n", &first);
13878 first.analyzing = FALSE;
13880 thinkOutput[0] = NULLCHAR;
13884 EditPositionDone (Boolean fakeRights)
13886 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13888 startedFromSetupPosition = TRUE;
13889 InitChessProgram(&first, FALSE);
13890 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13891 boards[0][EP_STATUS] = EP_NONE;
13892 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13893 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13894 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13895 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13896 } else boards[0][CASTLING][2] = NoRights;
13897 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13898 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13899 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13900 } else boards[0][CASTLING][5] = NoRights;
13902 SendToProgram("force\n", &first);
13903 if (blackPlaysFirst) {
13904 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13905 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13906 currentMove = forwardMostMove = backwardMostMove = 1;
13907 CopyBoard(boards[1], boards[0]);
13909 currentMove = forwardMostMove = backwardMostMove = 0;
13911 SendBoard(&first, forwardMostMove);
13912 if (appData.debugMode) {
13913 fprintf(debugFP, "EditPosDone\n");
13916 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13917 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13918 gameMode = EditGame;
13920 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13921 ClearHighlights(); /* [AS] */
13924 /* Pause for `ms' milliseconds */
13925 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13927 TimeDelay (long ms)
13934 } while (SubtractTimeMarks(&m2, &m1) < ms);
13937 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13939 SendMultiLineToICS (char *buf)
13941 char temp[MSG_SIZ+1], *p;
13948 strncpy(temp, buf, len);
13953 if (*p == '\n' || *p == '\r')
13958 strcat(temp, "\n");
13960 SendToPlayer(temp, strlen(temp));
13964 SetWhiteToPlayEvent ()
13966 if (gameMode == EditPosition) {
13967 blackPlaysFirst = FALSE;
13968 DisplayBothClocks(); /* works because currentMove is 0 */
13969 } else if (gameMode == IcsExamining) {
13970 SendToICS(ics_prefix);
13971 SendToICS("tomove white\n");
13976 SetBlackToPlayEvent ()
13978 if (gameMode == EditPosition) {
13979 blackPlaysFirst = TRUE;
13980 currentMove = 1; /* kludge */
13981 DisplayBothClocks();
13983 } else if (gameMode == IcsExamining) {
13984 SendToICS(ics_prefix);
13985 SendToICS("tomove black\n");
13990 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13993 ChessSquare piece = boards[0][y][x];
13995 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13997 switch (selection) {
13999 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14000 SendToICS(ics_prefix);
14001 SendToICS("bsetup clear\n");
14002 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14003 SendToICS(ics_prefix);
14004 SendToICS("clearboard\n");
14006 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14007 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14008 for (y = 0; y < BOARD_HEIGHT; y++) {
14009 if (gameMode == IcsExamining) {
14010 if (boards[currentMove][y][x] != EmptySquare) {
14011 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14016 boards[0][y][x] = p;
14021 if (gameMode == EditPosition) {
14022 DrawPosition(FALSE, boards[0]);
14027 SetWhiteToPlayEvent();
14031 SetBlackToPlayEvent();
14035 if (gameMode == IcsExamining) {
14036 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14037 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14040 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14041 if(x == BOARD_LEFT-2) {
14042 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14043 boards[0][y][1] = 0;
14045 if(x == BOARD_RGHT+1) {
14046 if(y >= gameInfo.holdingsSize) break;
14047 boards[0][y][BOARD_WIDTH-2] = 0;
14050 boards[0][y][x] = EmptySquare;
14051 DrawPosition(FALSE, boards[0]);
14056 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14057 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14058 selection = (ChessSquare) (PROMOTED piece);
14059 } else if(piece == EmptySquare) selection = WhiteSilver;
14060 else selection = (ChessSquare)((int)piece - 1);
14064 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14065 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14066 selection = (ChessSquare) (DEMOTED piece);
14067 } else if(piece == EmptySquare) selection = BlackSilver;
14068 else selection = (ChessSquare)((int)piece + 1);
14073 if(gameInfo.variant == VariantShatranj ||
14074 gameInfo.variant == VariantXiangqi ||
14075 gameInfo.variant == VariantCourier ||
14076 gameInfo.variant == VariantMakruk )
14077 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14082 if(gameInfo.variant == VariantXiangqi)
14083 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14084 if(gameInfo.variant == VariantKnightmate)
14085 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14088 if (gameMode == IcsExamining) {
14089 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14090 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14091 PieceToChar(selection), AAA + x, ONE + y);
14094 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14096 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14097 n = PieceToNumber(selection - BlackPawn);
14098 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14099 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14100 boards[0][BOARD_HEIGHT-1-n][1]++;
14102 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14103 n = PieceToNumber(selection);
14104 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14105 boards[0][n][BOARD_WIDTH-1] = selection;
14106 boards[0][n][BOARD_WIDTH-2]++;
14109 boards[0][y][x] = selection;
14110 DrawPosition(TRUE, boards[0]);
14112 fromX = fromY = -1;
14120 DropMenuEvent (ChessSquare selection, int x, int y)
14122 ChessMove moveType;
14124 switch (gameMode) {
14125 case IcsPlayingWhite:
14126 case MachinePlaysBlack:
14127 if (!WhiteOnMove(currentMove)) {
14128 DisplayMoveError(_("It is Black's turn"));
14131 moveType = WhiteDrop;
14133 case IcsPlayingBlack:
14134 case MachinePlaysWhite:
14135 if (WhiteOnMove(currentMove)) {
14136 DisplayMoveError(_("It is White's turn"));
14139 moveType = BlackDrop;
14142 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14148 if (moveType == BlackDrop && selection < BlackPawn) {
14149 selection = (ChessSquare) ((int) selection
14150 + (int) BlackPawn - (int) WhitePawn);
14152 if (boards[currentMove][y][x] != EmptySquare) {
14153 DisplayMoveError(_("That square is occupied"));
14157 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14163 /* Accept a pending offer of any kind from opponent */
14165 if (appData.icsActive) {
14166 SendToICS(ics_prefix);
14167 SendToICS("accept\n");
14168 } else if (cmailMsgLoaded) {
14169 if (currentMove == cmailOldMove &&
14170 commentList[cmailOldMove] != NULL &&
14171 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14172 "Black offers a draw" : "White offers a draw")) {
14174 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14175 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14177 DisplayError(_("There is no pending offer on this move"), 0);
14178 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14181 /* Not used for offers from chess program */
14188 /* Decline a pending offer of any kind from opponent */
14190 if (appData.icsActive) {
14191 SendToICS(ics_prefix);
14192 SendToICS("decline\n");
14193 } else if (cmailMsgLoaded) {
14194 if (currentMove == cmailOldMove &&
14195 commentList[cmailOldMove] != NULL &&
14196 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14197 "Black offers a draw" : "White offers a draw")) {
14199 AppendComment(cmailOldMove, "Draw declined", TRUE);
14200 DisplayComment(cmailOldMove - 1, "Draw declined");
14203 DisplayError(_("There is no pending offer on this move"), 0);
14206 /* Not used for offers from chess program */
14213 /* Issue ICS rematch command */
14214 if (appData.icsActive) {
14215 SendToICS(ics_prefix);
14216 SendToICS("rematch\n");
14223 /* Call your opponent's flag (claim a win on time) */
14224 if (appData.icsActive) {
14225 SendToICS(ics_prefix);
14226 SendToICS("flag\n");
14228 switch (gameMode) {
14231 case MachinePlaysWhite:
14234 GameEnds(GameIsDrawn, "Both players ran out of time",
14237 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14239 DisplayError(_("Your opponent is not out of time"), 0);
14242 case MachinePlaysBlack:
14245 GameEnds(GameIsDrawn, "Both players ran out of time",
14248 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14250 DisplayError(_("Your opponent is not out of time"), 0);
14258 ClockClick (int which)
14259 { // [HGM] code moved to back-end from winboard.c
14260 if(which) { // black clock
14261 if (gameMode == EditPosition || gameMode == IcsExamining) {
14262 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14263 SetBlackToPlayEvent();
14264 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14265 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14266 } else if (shiftKey) {
14267 AdjustClock(which, -1);
14268 } else if (gameMode == IcsPlayingWhite ||
14269 gameMode == MachinePlaysBlack) {
14272 } else { // white clock
14273 if (gameMode == EditPosition || gameMode == IcsExamining) {
14274 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14275 SetWhiteToPlayEvent();
14276 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14277 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14278 } else if (shiftKey) {
14279 AdjustClock(which, -1);
14280 } else if (gameMode == IcsPlayingBlack ||
14281 gameMode == MachinePlaysWhite) {
14290 /* Offer draw or accept pending draw offer from opponent */
14292 if (appData.icsActive) {
14293 /* Note: tournament rules require draw offers to be
14294 made after you make your move but before you punch
14295 your clock. Currently ICS doesn't let you do that;
14296 instead, you immediately punch your clock after making
14297 a move, but you can offer a draw at any time. */
14299 SendToICS(ics_prefix);
14300 SendToICS("draw\n");
14301 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14302 } else if (cmailMsgLoaded) {
14303 if (currentMove == cmailOldMove &&
14304 commentList[cmailOldMove] != NULL &&
14305 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14306 "Black offers a draw" : "White offers a draw")) {
14307 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14308 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14309 } else if (currentMove == cmailOldMove + 1) {
14310 char *offer = WhiteOnMove(cmailOldMove) ?
14311 "White offers a draw" : "Black offers a draw";
14312 AppendComment(currentMove, offer, TRUE);
14313 DisplayComment(currentMove - 1, offer);
14314 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14316 DisplayError(_("You must make your move before offering a draw"), 0);
14317 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14319 } else if (first.offeredDraw) {
14320 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14322 if (first.sendDrawOffers) {
14323 SendToProgram("draw\n", &first);
14324 userOfferedDraw = TRUE;
14332 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14334 if (appData.icsActive) {
14335 SendToICS(ics_prefix);
14336 SendToICS("adjourn\n");
14338 /* Currently GNU Chess doesn't offer or accept Adjourns */
14346 /* Offer Abort or accept pending Abort offer from opponent */
14348 if (appData.icsActive) {
14349 SendToICS(ics_prefix);
14350 SendToICS("abort\n");
14352 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14359 /* Resign. You can do this even if it's not your turn. */
14361 if (appData.icsActive) {
14362 SendToICS(ics_prefix);
14363 SendToICS("resign\n");
14365 switch (gameMode) {
14366 case MachinePlaysWhite:
14367 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14369 case MachinePlaysBlack:
14370 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14373 if (cmailMsgLoaded) {
14375 if (WhiteOnMove(cmailOldMove)) {
14376 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14378 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14380 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14391 StopObservingEvent ()
14393 /* Stop observing current games */
14394 SendToICS(ics_prefix);
14395 SendToICS("unobserve\n");
14399 StopExaminingEvent ()
14401 /* Stop observing current game */
14402 SendToICS(ics_prefix);
14403 SendToICS("unexamine\n");
14407 ForwardInner (int target)
14409 int limit; int oldSeekGraphUp = seekGraphUp;
14411 if (appData.debugMode)
14412 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14413 target, currentMove, forwardMostMove);
14415 if (gameMode == EditPosition)
14418 seekGraphUp = FALSE;
14419 MarkTargetSquares(1);
14421 if (gameMode == PlayFromGameFile && !pausing)
14424 if (gameMode == IcsExamining && pausing)
14425 limit = pauseExamForwardMostMove;
14427 limit = forwardMostMove;
14429 if (target > limit) target = limit;
14431 if (target > 0 && moveList[target - 1][0]) {
14432 int fromX, fromY, toX, toY;
14433 toX = moveList[target - 1][2] - AAA;
14434 toY = moveList[target - 1][3] - ONE;
14435 if (moveList[target - 1][1] == '@') {
14436 if (appData.highlightLastMove) {
14437 SetHighlights(-1, -1, toX, toY);
14440 fromX = moveList[target - 1][0] - AAA;
14441 fromY = moveList[target - 1][1] - ONE;
14442 if (target == currentMove + 1) {
14443 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14445 if (appData.highlightLastMove) {
14446 SetHighlights(fromX, fromY, toX, toY);
14450 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14451 gameMode == Training || gameMode == PlayFromGameFile ||
14452 gameMode == AnalyzeFile) {
14453 while (currentMove < target) {
14454 SendMoveToProgram(currentMove++, &first);
14457 currentMove = target;
14460 if (gameMode == EditGame || gameMode == EndOfGame) {
14461 whiteTimeRemaining = timeRemaining[0][currentMove];
14462 blackTimeRemaining = timeRemaining[1][currentMove];
14464 DisplayBothClocks();
14465 DisplayMove(currentMove - 1);
14466 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14467 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14468 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14469 DisplayComment(currentMove - 1, commentList[currentMove]);
14471 ClearMap(); // [HGM] exclude: invalidate map
14478 if (gameMode == IcsExamining && !pausing) {
14479 SendToICS(ics_prefix);
14480 SendToICS("forward\n");
14482 ForwardInner(currentMove + 1);
14489 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14490 /* to optimze, we temporarily turn off analysis mode while we feed
14491 * the remaining moves to the engine. Otherwise we get analysis output
14494 if (first.analysisSupport) {
14495 SendToProgram("exit\nforce\n", &first);
14496 first.analyzing = FALSE;
14500 if (gameMode == IcsExamining && !pausing) {
14501 SendToICS(ics_prefix);
14502 SendToICS("forward 999999\n");
14504 ForwardInner(forwardMostMove);
14507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14508 /* we have fed all the moves, so reactivate analysis mode */
14509 SendToProgram("analyze\n", &first);
14510 first.analyzing = TRUE;
14511 /*first.maybeThinking = TRUE;*/
14512 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14517 BackwardInner (int target)
14519 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14521 if (appData.debugMode)
14522 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14523 target, currentMove, forwardMostMove);
14525 if (gameMode == EditPosition) return;
14526 seekGraphUp = FALSE;
14527 MarkTargetSquares(1);
14528 if (currentMove <= backwardMostMove) {
14530 DrawPosition(full_redraw, boards[currentMove]);
14533 if (gameMode == PlayFromGameFile && !pausing)
14536 if (moveList[target][0]) {
14537 int fromX, fromY, toX, toY;
14538 toX = moveList[target][2] - AAA;
14539 toY = moveList[target][3] - ONE;
14540 if (moveList[target][1] == '@') {
14541 if (appData.highlightLastMove) {
14542 SetHighlights(-1, -1, toX, toY);
14545 fromX = moveList[target][0] - AAA;
14546 fromY = moveList[target][1] - ONE;
14547 if (target == currentMove - 1) {
14548 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14550 if (appData.highlightLastMove) {
14551 SetHighlights(fromX, fromY, toX, toY);
14555 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14556 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14557 while (currentMove > target) {
14558 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14559 // null move cannot be undone. Reload program with move history before it.
14561 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14562 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14564 SendBoard(&first, i);
14565 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14568 SendToProgram("undo\n", &first);
14572 currentMove = target;
14575 if (gameMode == EditGame || gameMode == EndOfGame) {
14576 whiteTimeRemaining = timeRemaining[0][currentMove];
14577 blackTimeRemaining = timeRemaining[1][currentMove];
14579 DisplayBothClocks();
14580 DisplayMove(currentMove - 1);
14581 DrawPosition(full_redraw, boards[currentMove]);
14582 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14583 // [HGM] PV info: routine tests if comment empty
14584 DisplayComment(currentMove - 1, commentList[currentMove]);
14585 ClearMap(); // [HGM] exclude: invalidate map
14591 if (gameMode == IcsExamining && !pausing) {
14592 SendToICS(ics_prefix);
14593 SendToICS("backward\n");
14595 BackwardInner(currentMove - 1);
14602 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14603 /* to optimize, we temporarily turn off analysis mode while we undo
14604 * all the moves. Otherwise we get analysis output after each undo.
14606 if (first.analysisSupport) {
14607 SendToProgram("exit\nforce\n", &first);
14608 first.analyzing = FALSE;
14612 if (gameMode == IcsExamining && !pausing) {
14613 SendToICS(ics_prefix);
14614 SendToICS("backward 999999\n");
14616 BackwardInner(backwardMostMove);
14619 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14620 /* we have fed all the moves, so reactivate analysis mode */
14621 SendToProgram("analyze\n", &first);
14622 first.analyzing = TRUE;
14623 /*first.maybeThinking = TRUE;*/
14624 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14631 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14632 if (to >= forwardMostMove) to = forwardMostMove;
14633 if (to <= backwardMostMove) to = backwardMostMove;
14634 if (to < currentMove) {
14642 RevertEvent (Boolean annotate)
14644 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14647 if (gameMode != IcsExamining) {
14648 DisplayError(_("You are not examining a game"), 0);
14652 DisplayError(_("You can't revert while pausing"), 0);
14655 SendToICS(ics_prefix);
14656 SendToICS("revert\n");
14660 RetractMoveEvent ()
14662 switch (gameMode) {
14663 case MachinePlaysWhite:
14664 case MachinePlaysBlack:
14665 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14666 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14669 if (forwardMostMove < 2) return;
14670 currentMove = forwardMostMove = forwardMostMove - 2;
14671 whiteTimeRemaining = timeRemaining[0][currentMove];
14672 blackTimeRemaining = timeRemaining[1][currentMove];
14673 DisplayBothClocks();
14674 DisplayMove(currentMove - 1);
14675 ClearHighlights();/*!! could figure this out*/
14676 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14677 SendToProgram("remove\n", &first);
14678 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14681 case BeginningOfGame:
14685 case IcsPlayingWhite:
14686 case IcsPlayingBlack:
14687 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14688 SendToICS(ics_prefix);
14689 SendToICS("takeback 2\n");
14691 SendToICS(ics_prefix);
14692 SendToICS("takeback 1\n");
14701 ChessProgramState *cps;
14703 switch (gameMode) {
14704 case MachinePlaysWhite:
14705 if (!WhiteOnMove(forwardMostMove)) {
14706 DisplayError(_("It is your turn"), 0);
14711 case MachinePlaysBlack:
14712 if (WhiteOnMove(forwardMostMove)) {
14713 DisplayError(_("It is your turn"), 0);
14718 case TwoMachinesPlay:
14719 if (WhiteOnMove(forwardMostMove) ==
14720 (first.twoMachinesColor[0] == 'w')) {
14726 case BeginningOfGame:
14730 SendToProgram("?\n", cps);
14734 TruncateGameEvent ()
14737 if (gameMode != EditGame) return;
14744 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14745 if (forwardMostMove > currentMove) {
14746 if (gameInfo.resultDetails != NULL) {
14747 free(gameInfo.resultDetails);
14748 gameInfo.resultDetails = NULL;
14749 gameInfo.result = GameUnfinished;
14751 forwardMostMove = currentMove;
14752 HistorySet(parseList, backwardMostMove, forwardMostMove,
14760 if (appData.noChessProgram) return;
14761 switch (gameMode) {
14762 case MachinePlaysWhite:
14763 if (WhiteOnMove(forwardMostMove)) {
14764 DisplayError(_("Wait until your turn"), 0);
14768 case BeginningOfGame:
14769 case MachinePlaysBlack:
14770 if (!WhiteOnMove(forwardMostMove)) {
14771 DisplayError(_("Wait until your turn"), 0);
14776 DisplayError(_("No hint available"), 0);
14779 SendToProgram("hint\n", &first);
14780 hintRequested = TRUE;
14786 if (appData.noChessProgram) return;
14787 switch (gameMode) {
14788 case MachinePlaysWhite:
14789 if (WhiteOnMove(forwardMostMove)) {
14790 DisplayError(_("Wait until your turn"), 0);
14794 case BeginningOfGame:
14795 case MachinePlaysBlack:
14796 if (!WhiteOnMove(forwardMostMove)) {
14797 DisplayError(_("Wait until your turn"), 0);
14802 EditPositionDone(TRUE);
14804 case TwoMachinesPlay:
14809 SendToProgram("bk\n", &first);
14810 bookOutput[0] = NULLCHAR;
14811 bookRequested = TRUE;
14817 char *tags = PGNTags(&gameInfo);
14818 TagsPopUp(tags, CmailMsg());
14822 /* end button procedures */
14825 PrintPosition (FILE *fp, int move)
14829 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14830 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14831 char c = PieceToChar(boards[move][i][j]);
14832 fputc(c == 'x' ? '.' : c, fp);
14833 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14836 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14837 fprintf(fp, "white to play\n");
14839 fprintf(fp, "black to play\n");
14843 PrintOpponents (FILE *fp)
14845 if (gameInfo.white != NULL) {
14846 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14852 /* Find last component of program's own name, using some heuristics */
14854 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14857 int local = (strcmp(host, "localhost") == 0);
14858 while (!local && (p = strchr(prog, ';')) != NULL) {
14860 while (*p == ' ') p++;
14863 if (*prog == '"' || *prog == '\'') {
14864 q = strchr(prog + 1, *prog);
14866 q = strchr(prog, ' ');
14868 if (q == NULL) q = prog + strlen(prog);
14870 while (p >= prog && *p != '/' && *p != '\\') p--;
14872 if(p == prog && *p == '"') p++;
14874 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14875 memcpy(buf, p, q - p);
14876 buf[q - p] = NULLCHAR;
14884 TimeControlTagValue ()
14887 if (!appData.clockMode) {
14888 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14889 } else if (movesPerSession > 0) {
14890 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14891 } else if (timeIncrement == 0) {
14892 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14894 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14896 return StrSave(buf);
14902 /* This routine is used only for certain modes */
14903 VariantClass v = gameInfo.variant;
14904 ChessMove r = GameUnfinished;
14907 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14908 r = gameInfo.result;
14909 p = gameInfo.resultDetails;
14910 gameInfo.resultDetails = NULL;
14912 ClearGameInfo(&gameInfo);
14913 gameInfo.variant = v;
14915 switch (gameMode) {
14916 case MachinePlaysWhite:
14917 gameInfo.event = StrSave( appData.pgnEventHeader );
14918 gameInfo.site = StrSave(HostName());
14919 gameInfo.date = PGNDate();
14920 gameInfo.round = StrSave("-");
14921 gameInfo.white = StrSave(first.tidy);
14922 gameInfo.black = StrSave(UserName());
14923 gameInfo.timeControl = TimeControlTagValue();
14926 case MachinePlaysBlack:
14927 gameInfo.event = StrSave( appData.pgnEventHeader );
14928 gameInfo.site = StrSave(HostName());
14929 gameInfo.date = PGNDate();
14930 gameInfo.round = StrSave("-");
14931 gameInfo.white = StrSave(UserName());
14932 gameInfo.black = StrSave(first.tidy);
14933 gameInfo.timeControl = TimeControlTagValue();
14936 case TwoMachinesPlay:
14937 gameInfo.event = StrSave( appData.pgnEventHeader );
14938 gameInfo.site = StrSave(HostName());
14939 gameInfo.date = PGNDate();
14942 snprintf(buf, MSG_SIZ, "%d", roundNr);
14943 gameInfo.round = StrSave(buf);
14945 gameInfo.round = StrSave("-");
14947 if (first.twoMachinesColor[0] == 'w') {
14948 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14949 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14951 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14952 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14954 gameInfo.timeControl = TimeControlTagValue();
14958 gameInfo.event = StrSave("Edited game");
14959 gameInfo.site = StrSave(HostName());
14960 gameInfo.date = PGNDate();
14961 gameInfo.round = StrSave("-");
14962 gameInfo.white = StrSave("-");
14963 gameInfo.black = StrSave("-");
14964 gameInfo.result = r;
14965 gameInfo.resultDetails = p;
14969 gameInfo.event = StrSave("Edited position");
14970 gameInfo.site = StrSave(HostName());
14971 gameInfo.date = PGNDate();
14972 gameInfo.round = StrSave("-");
14973 gameInfo.white = StrSave("-");
14974 gameInfo.black = StrSave("-");
14977 case IcsPlayingWhite:
14978 case IcsPlayingBlack:
14983 case PlayFromGameFile:
14984 gameInfo.event = StrSave("Game from non-PGN file");
14985 gameInfo.site = StrSave(HostName());
14986 gameInfo.date = PGNDate();
14987 gameInfo.round = StrSave("-");
14988 gameInfo.white = StrSave("?");
14989 gameInfo.black = StrSave("?");
14998 ReplaceComment (int index, char *text)
15004 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15005 pvInfoList[index-1].depth == len &&
15006 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15007 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15008 while (*text == '\n') text++;
15009 len = strlen(text);
15010 while (len > 0 && text[len - 1] == '\n') len--;
15012 if (commentList[index] != NULL)
15013 free(commentList[index]);
15016 commentList[index] = NULL;
15019 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15020 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15021 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15022 commentList[index] = (char *) malloc(len + 2);
15023 strncpy(commentList[index], text, len);
15024 commentList[index][len] = '\n';
15025 commentList[index][len + 1] = NULLCHAR;
15027 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15029 commentList[index] = (char *) malloc(len + 7);
15030 safeStrCpy(commentList[index], "{\n", 3);
15031 safeStrCpy(commentList[index]+2, text, len+1);
15032 commentList[index][len+2] = NULLCHAR;
15033 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15034 strcat(commentList[index], "\n}\n");
15039 CrushCRs (char *text)
15047 if (ch == '\r') continue;
15049 } while (ch != '\0');
15053 AppendComment (int index, char *text, Boolean addBraces)
15054 /* addBraces tells if we should add {} */
15059 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15060 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15063 while (*text == '\n') text++;
15064 len = strlen(text);
15065 while (len > 0 && text[len - 1] == '\n') len--;
15066 text[len] = NULLCHAR;
15068 if (len == 0) return;
15070 if (commentList[index] != NULL) {
15071 Boolean addClosingBrace = addBraces;
15072 old = commentList[index];
15073 oldlen = strlen(old);
15074 while(commentList[index][oldlen-1] == '\n')
15075 commentList[index][--oldlen] = NULLCHAR;
15076 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15077 safeStrCpy(commentList[index], old, oldlen + len + 6);
15079 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15080 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15081 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15082 while (*text == '\n') { text++; len--; }
15083 commentList[index][--oldlen] = NULLCHAR;
15085 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15086 else strcat(commentList[index], "\n");
15087 strcat(commentList[index], text);
15088 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15089 else strcat(commentList[index], "\n");
15091 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15093 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15094 else commentList[index][0] = NULLCHAR;
15095 strcat(commentList[index], text);
15096 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15097 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15102 FindStr (char * text, char * sub_text)
15104 char * result = strstr( text, sub_text );
15106 if( result != NULL ) {
15107 result += strlen( sub_text );
15113 /* [AS] Try to extract PV info from PGN comment */
15114 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15116 GetInfoFromComment (int index, char * text)
15118 char * sep = text, *p;
15120 if( text != NULL && index > 0 ) {
15123 int time = -1, sec = 0, deci;
15124 char * s_eval = FindStr( text, "[%eval " );
15125 char * s_emt = FindStr( text, "[%emt " );
15127 if( s_eval != NULL || s_emt != NULL ) {
15131 if( s_eval != NULL ) {
15132 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15136 if( delim != ']' ) {
15141 if( s_emt != NULL ) {
15146 /* We expect something like: [+|-]nnn.nn/dd */
15149 if(*text != '{') return text; // [HGM] braces: must be normal comment
15151 sep = strchr( text, '/' );
15152 if( sep == NULL || sep < (text+4) ) {
15157 if(p[1] == '(') { // comment starts with PV
15158 p = strchr(p, ')'); // locate end of PV
15159 if(p == NULL || sep < p+5) return text;
15160 // at this point we have something like "{(.*) +0.23/6 ..."
15161 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15162 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15163 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15165 time = -1; sec = -1; deci = -1;
15166 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15167 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15168 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15169 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15173 if( score_lo < 0 || score_lo >= 100 ) {
15177 if(sec >= 0) time = 600*time + 10*sec; else
15178 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15180 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15182 /* [HGM] PV time: now locate end of PV info */
15183 while( *++sep >= '0' && *sep <= '9'); // strip depth
15185 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15187 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15189 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15190 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15201 pvInfoList[index-1].depth = depth;
15202 pvInfoList[index-1].score = score;
15203 pvInfoList[index-1].time = 10*time; // centi-sec
15204 if(*sep == '}') *sep = 0; else *--sep = '{';
15205 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15211 SendToProgram (char *message, ChessProgramState *cps)
15213 int count, outCount, error;
15216 if (cps->pr == NoProc) return;
15219 if (appData.debugMode) {
15222 fprintf(debugFP, "%ld >%-6s: %s",
15223 SubtractTimeMarks(&now, &programStartTime),
15224 cps->which, message);
15226 fprintf(serverFP, "%ld >%-6s: %s",
15227 SubtractTimeMarks(&now, &programStartTime),
15228 cps->which, message), fflush(serverFP);
15231 count = strlen(message);
15232 outCount = OutputToProcess(cps->pr, message, count, &error);
15233 if (outCount < count && !exiting
15234 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15235 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15236 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15237 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15238 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15239 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15240 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15241 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15243 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15244 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15245 gameInfo.result = res;
15247 gameInfo.resultDetails = StrSave(buf);
15249 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15250 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15255 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15259 ChessProgramState *cps = (ChessProgramState *)closure;
15261 if (isr != cps->isr) return; /* Killed intentionally */
15264 RemoveInputSource(cps->isr);
15265 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15266 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15267 _(cps->which), cps->program);
15268 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15269 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15270 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15271 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15272 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15274 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15275 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15276 gameInfo.result = res;
15278 gameInfo.resultDetails = StrSave(buf);
15280 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15281 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15283 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15284 _(cps->which), cps->program);
15285 RemoveInputSource(cps->isr);
15287 /* [AS] Program is misbehaving badly... kill it */
15288 if( count == -2 ) {
15289 DestroyChildProcess( cps->pr, 9 );
15293 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15298 if ((end_str = strchr(message, '\r')) != NULL)
15299 *end_str = NULLCHAR;
15300 if ((end_str = strchr(message, '\n')) != NULL)
15301 *end_str = NULLCHAR;
15303 if (appData.debugMode) {
15304 TimeMark now; int print = 1;
15305 char *quote = ""; char c; int i;
15307 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15308 char start = message[0];
15309 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15310 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15311 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15312 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15313 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15314 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15315 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15316 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15317 sscanf(message, "hint: %c", &c)!=1 &&
15318 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15319 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15320 print = (appData.engineComments >= 2);
15322 message[0] = start; // restore original message
15326 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15327 SubtractTimeMarks(&now, &programStartTime), cps->which,
15331 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15332 SubtractTimeMarks(&now, &programStartTime), cps->which,
15334 message), fflush(serverFP);
15338 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15339 if (appData.icsEngineAnalyze) {
15340 if (strstr(message, "whisper") != NULL ||
15341 strstr(message, "kibitz") != NULL ||
15342 strstr(message, "tellics") != NULL) return;
15345 HandleMachineMove(message, cps);
15350 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15355 if( timeControl_2 > 0 ) {
15356 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15357 tc = timeControl_2;
15360 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15361 inc /= cps->timeOdds;
15362 st /= cps->timeOdds;
15364 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15367 /* Set exact time per move, normally using st command */
15368 if (cps->stKludge) {
15369 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15371 if (seconds == 0) {
15372 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15374 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15377 snprintf(buf, MSG_SIZ, "st %d\n", st);
15380 /* Set conventional or incremental time control, using level command */
15381 if (seconds == 0) {
15382 /* Note old gnuchess bug -- minutes:seconds used to not work.
15383 Fixed in later versions, but still avoid :seconds
15384 when seconds is 0. */
15385 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15387 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15388 seconds, inc/1000.);
15391 SendToProgram(buf, cps);
15393 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15394 /* Orthogonally, limit search to given depth */
15396 if (cps->sdKludge) {
15397 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15399 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15401 SendToProgram(buf, cps);
15404 if(cps->nps >= 0) { /* [HGM] nps */
15405 if(cps->supportsNPS == FALSE)
15406 cps->nps = -1; // don't use if engine explicitly says not supported!
15408 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15409 SendToProgram(buf, cps);
15414 ChessProgramState *
15416 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15418 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15419 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15425 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15427 char message[MSG_SIZ];
15430 /* Note: this routine must be called when the clocks are stopped
15431 or when they have *just* been set or switched; otherwise
15432 it will be off by the time since the current tick started.
15434 if (machineWhite) {
15435 time = whiteTimeRemaining / 10;
15436 otime = blackTimeRemaining / 10;
15438 time = blackTimeRemaining / 10;
15439 otime = whiteTimeRemaining / 10;
15441 /* [HGM] translate opponent's time by time-odds factor */
15442 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15444 if (time <= 0) time = 1;
15445 if (otime <= 0) otime = 1;
15447 snprintf(message, MSG_SIZ, "time %ld\n", time);
15448 SendToProgram(message, cps);
15450 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15451 SendToProgram(message, cps);
15455 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15458 int len = strlen(name);
15461 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15463 sscanf(*p, "%d", &val);
15465 while (**p && **p != ' ')
15467 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15468 SendToProgram(buf, cps);
15475 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15478 int len = strlen(name);
15479 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15481 sscanf(*p, "%d", loc);
15482 while (**p && **p != ' ') (*p)++;
15483 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15484 SendToProgram(buf, cps);
15491 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15494 int len = strlen(name);
15495 if (strncmp((*p), name, len) == 0
15496 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15498 sscanf(*p, "%[^\"]", loc);
15499 while (**p && **p != '\"') (*p)++;
15500 if (**p == '\"') (*p)++;
15501 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15502 SendToProgram(buf, cps);
15509 ParseOption (Option *opt, ChessProgramState *cps)
15510 // [HGM] options: process the string that defines an engine option, and determine
15511 // name, type, default value, and allowed value range
15513 char *p, *q, buf[MSG_SIZ];
15514 int n, min = (-1)<<31, max = 1<<31, def;
15516 if(p = strstr(opt->name, " -spin ")) {
15517 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15518 if(max < min) max = min; // enforce consistency
15519 if(def < min) def = min;
15520 if(def > max) def = max;
15525 } else if((p = strstr(opt->name, " -slider "))) {
15526 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15527 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15528 if(max < min) max = min; // enforce consistency
15529 if(def < min) def = min;
15530 if(def > max) def = max;
15534 opt->type = Spin; // Slider;
15535 } else if((p = strstr(opt->name, " -string "))) {
15536 opt->textValue = p+9;
15537 opt->type = TextBox;
15538 } else if((p = strstr(opt->name, " -file "))) {
15539 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15540 opt->textValue = p+7;
15541 opt->type = FileName; // FileName;
15542 } else if((p = strstr(opt->name, " -path "))) {
15543 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15544 opt->textValue = p+7;
15545 opt->type = PathName; // PathName;
15546 } else if(p = strstr(opt->name, " -check ")) {
15547 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15548 opt->value = (def != 0);
15549 opt->type = CheckBox;
15550 } else if(p = strstr(opt->name, " -combo ")) {
15551 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15552 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15553 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15554 opt->value = n = 0;
15555 while(q = StrStr(q, " /// ")) {
15556 n++; *q = 0; // count choices, and null-terminate each of them
15558 if(*q == '*') { // remember default, which is marked with * prefix
15562 cps->comboList[cps->comboCnt++] = q;
15564 cps->comboList[cps->comboCnt++] = NULL;
15566 opt->type = ComboBox;
15567 } else if(p = strstr(opt->name, " -button")) {
15568 opt->type = Button;
15569 } else if(p = strstr(opt->name, " -save")) {
15570 opt->type = SaveButton;
15571 } else return FALSE;
15572 *p = 0; // terminate option name
15573 // now look if the command-line options define a setting for this engine option.
15574 if(cps->optionSettings && cps->optionSettings[0])
15575 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15576 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15577 snprintf(buf, MSG_SIZ, "option %s", p);
15578 if(p = strstr(buf, ",")) *p = 0;
15579 if(q = strchr(buf, '=')) switch(opt->type) {
15581 for(n=0; n<opt->max; n++)
15582 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15585 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15589 opt->value = atoi(q+1);
15594 SendToProgram(buf, cps);
15600 FeatureDone (ChessProgramState *cps, int val)
15602 DelayedEventCallback cb = GetDelayedEvent();
15603 if ((cb == InitBackEnd3 && cps == &first) ||
15604 (cb == SettingsMenuIfReady && cps == &second) ||
15605 (cb == LoadEngine) ||
15606 (cb == TwoMachinesEventIfReady)) {
15607 CancelDelayedEvent();
15608 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15610 cps->initDone = val;
15613 /* Parse feature command from engine */
15615 ParseFeatures (char *args, ChessProgramState *cps)
15623 while (*p == ' ') p++;
15624 if (*p == NULLCHAR) return;
15626 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15627 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15628 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15629 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15630 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15631 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15632 if (BoolFeature(&p, "reuse", &val, cps)) {
15633 /* Engine can disable reuse, but can't enable it if user said no */
15634 if (!val) cps->reuse = FALSE;
15637 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15638 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15639 if (gameMode == TwoMachinesPlay) {
15640 DisplayTwoMachinesTitle();
15646 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15647 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15648 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15649 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15650 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15651 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15652 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15653 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15654 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15655 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15656 if (IntFeature(&p, "done", &val, cps)) {
15657 FeatureDone(cps, val);
15660 /* Added by Tord: */
15661 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15662 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15663 /* End of additions by Tord */
15665 /* [HGM] added features: */
15666 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15667 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15668 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15669 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15670 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15671 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15672 if (StringFeature(&p, "option", buf, cps)) {
15673 FREE(cps->option[cps->nrOptions].name);
15674 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15675 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15676 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15677 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15678 SendToProgram(buf, cps);
15681 if(cps->nrOptions >= MAX_OPTIONS) {
15683 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15684 DisplayError(buf, 0);
15688 /* End of additions by HGM */
15690 /* unknown feature: complain and skip */
15692 while (*q && *q != '=') q++;
15693 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15694 SendToProgram(buf, cps);
15700 while (*p && *p != '\"') p++;
15701 if (*p == '\"') p++;
15703 while (*p && *p != ' ') p++;
15711 PeriodicUpdatesEvent (int newState)
15713 if (newState == appData.periodicUpdates)
15716 appData.periodicUpdates=newState;
15718 /* Display type changes, so update it now */
15719 // DisplayAnalysis();
15721 /* Get the ball rolling again... */
15723 AnalysisPeriodicEvent(1);
15724 StartAnalysisClock();
15729 PonderNextMoveEvent (int newState)
15731 if (newState == appData.ponderNextMove) return;
15732 if (gameMode == EditPosition) EditPositionDone(TRUE);
15734 SendToProgram("hard\n", &first);
15735 if (gameMode == TwoMachinesPlay) {
15736 SendToProgram("hard\n", &second);
15739 SendToProgram("easy\n", &first);
15740 thinkOutput[0] = NULLCHAR;
15741 if (gameMode == TwoMachinesPlay) {
15742 SendToProgram("easy\n", &second);
15745 appData.ponderNextMove = newState;
15749 NewSettingEvent (int option, int *feature, char *command, int value)
15753 if (gameMode == EditPosition) EditPositionDone(TRUE);
15754 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15755 if(feature == NULL || *feature) SendToProgram(buf, &first);
15756 if (gameMode == TwoMachinesPlay) {
15757 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15762 ShowThinkingEvent ()
15763 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15765 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15766 int newState = appData.showThinking
15767 // [HGM] thinking: other features now need thinking output as well
15768 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15770 if (oldState == newState) return;
15771 oldState = newState;
15772 if (gameMode == EditPosition) EditPositionDone(TRUE);
15774 SendToProgram("post\n", &first);
15775 if (gameMode == TwoMachinesPlay) {
15776 SendToProgram("post\n", &second);
15779 SendToProgram("nopost\n", &first);
15780 thinkOutput[0] = NULLCHAR;
15781 if (gameMode == TwoMachinesPlay) {
15782 SendToProgram("nopost\n", &second);
15785 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15789 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15791 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15792 if (pr == NoProc) return;
15793 AskQuestion(title, question, replyPrefix, pr);
15797 TypeInEvent (char firstChar)
15799 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15800 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15801 gameMode == AnalyzeMode || gameMode == EditGame ||
15802 gameMode == EditPosition || gameMode == IcsExamining ||
15803 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15804 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15805 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15806 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15807 gameMode == Training) PopUpMoveDialog(firstChar);
15811 TypeInDoneEvent (char *move)
15814 int n, fromX, fromY, toX, toY;
15816 ChessMove moveType;
15819 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15820 EditPositionPasteFEN(move);
15823 // [HGM] movenum: allow move number to be typed in any mode
15824 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15828 // undocumented kludge: allow command-line option to be typed in!
15829 // (potentially fatal, and does not implement the effect of the option.)
15830 // should only be used for options that are values on which future decisions will be made,
15831 // and definitely not on options that would be used during initialization.
15832 if(strstr(move, "!!! -") == move) {
15833 ParseArgsFromString(move+4);
15837 if (gameMode != EditGame && currentMove != forwardMostMove &&
15838 gameMode != Training) {
15839 DisplayMoveError(_("Displayed move is not current"));
15841 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15842 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15843 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15844 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15845 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15846 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15848 DisplayMoveError(_("Could not parse move"));
15854 DisplayMove (int moveNumber)
15856 char message[MSG_SIZ];
15858 char cpThinkOutput[MSG_SIZ];
15860 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15862 if (moveNumber == forwardMostMove - 1 ||
15863 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15865 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15867 if (strchr(cpThinkOutput, '\n')) {
15868 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15871 *cpThinkOutput = NULLCHAR;
15874 /* [AS] Hide thinking from human user */
15875 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15876 *cpThinkOutput = NULLCHAR;
15877 if( thinkOutput[0] != NULLCHAR ) {
15880 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15881 cpThinkOutput[i] = '.';
15883 cpThinkOutput[i] = NULLCHAR;
15884 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15888 if (moveNumber == forwardMostMove - 1 &&
15889 gameInfo.resultDetails != NULL) {
15890 if (gameInfo.resultDetails[0] == NULLCHAR) {
15891 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15893 snprintf(res, MSG_SIZ, " {%s} %s",
15894 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15900 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15901 DisplayMessage(res, cpThinkOutput);
15903 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15904 WhiteOnMove(moveNumber) ? " " : ".. ",
15905 parseList[moveNumber], res);
15906 DisplayMessage(message, cpThinkOutput);
15911 DisplayComment (int moveNumber, char *text)
15913 char title[MSG_SIZ];
15915 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15916 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15918 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15919 WhiteOnMove(moveNumber) ? " " : ".. ",
15920 parseList[moveNumber]);
15922 if (text != NULL && (appData.autoDisplayComment || commentUp))
15923 CommentPopUp(title, text);
15926 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15927 * might be busy thinking or pondering. It can be omitted if your
15928 * gnuchess is configured to stop thinking immediately on any user
15929 * input. However, that gnuchess feature depends on the FIONREAD
15930 * ioctl, which does not work properly on some flavors of Unix.
15933 Attention (ChessProgramState *cps)
15936 if (!cps->useSigint) return;
15937 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15938 switch (gameMode) {
15939 case MachinePlaysWhite:
15940 case MachinePlaysBlack:
15941 case TwoMachinesPlay:
15942 case IcsPlayingWhite:
15943 case IcsPlayingBlack:
15946 /* Skip if we know it isn't thinking */
15947 if (!cps->maybeThinking) return;
15948 if (appData.debugMode)
15949 fprintf(debugFP, "Interrupting %s\n", cps->which);
15950 InterruptChildProcess(cps->pr);
15951 cps->maybeThinking = FALSE;
15956 #endif /*ATTENTION*/
15962 if (whiteTimeRemaining <= 0) {
15965 if (appData.icsActive) {
15966 if (appData.autoCallFlag &&
15967 gameMode == IcsPlayingBlack && !blackFlag) {
15968 SendToICS(ics_prefix);
15969 SendToICS("flag\n");
15973 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15975 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15976 if (appData.autoCallFlag) {
15977 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15984 if (blackTimeRemaining <= 0) {
15987 if (appData.icsActive) {
15988 if (appData.autoCallFlag &&
15989 gameMode == IcsPlayingWhite && !whiteFlag) {
15990 SendToICS(ics_prefix);
15991 SendToICS("flag\n");
15995 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15997 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15998 if (appData.autoCallFlag) {
15999 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16010 CheckTimeControl ()
16012 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16013 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16016 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16018 if ( !WhiteOnMove(forwardMostMove) ) {
16019 /* White made time control */
16020 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16021 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16022 /* [HGM] time odds: correct new time quota for time odds! */
16023 / WhitePlayer()->timeOdds;
16024 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16026 lastBlack -= blackTimeRemaining;
16027 /* Black made time control */
16028 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16029 / WhitePlayer()->other->timeOdds;
16030 lastWhite = whiteTimeRemaining;
16035 DisplayBothClocks ()
16037 int wom = gameMode == EditPosition ?
16038 !blackPlaysFirst : WhiteOnMove(currentMove);
16039 DisplayWhiteClock(whiteTimeRemaining, wom);
16040 DisplayBlackClock(blackTimeRemaining, !wom);
16044 /* Timekeeping seems to be a portability nightmare. I think everyone
16045 has ftime(), but I'm really not sure, so I'm including some ifdefs
16046 to use other calls if you don't. Clocks will be less accurate if
16047 you have neither ftime nor gettimeofday.
16050 /* VS 2008 requires the #include outside of the function */
16051 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16052 #include <sys/timeb.h>
16055 /* Get the current time as a TimeMark */
16057 GetTimeMark (TimeMark *tm)
16059 #if HAVE_GETTIMEOFDAY
16061 struct timeval timeVal;
16062 struct timezone timeZone;
16064 gettimeofday(&timeVal, &timeZone);
16065 tm->sec = (long) timeVal.tv_sec;
16066 tm->ms = (int) (timeVal.tv_usec / 1000L);
16068 #else /*!HAVE_GETTIMEOFDAY*/
16071 // include <sys/timeb.h> / moved to just above start of function
16072 struct timeb timeB;
16075 tm->sec = (long) timeB.time;
16076 tm->ms = (int) timeB.millitm;
16078 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16079 tm->sec = (long) time(NULL);
16085 /* Return the difference in milliseconds between two
16086 time marks. We assume the difference will fit in a long!
16089 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16091 return 1000L*(tm2->sec - tm1->sec) +
16092 (long) (tm2->ms - tm1->ms);
16097 * Code to manage the game clocks.
16099 * In tournament play, black starts the clock and then white makes a move.
16100 * We give the human user a slight advantage if he is playing white---the
16101 * clocks don't run until he makes his first move, so it takes zero time.
16102 * Also, we don't account for network lag, so we could get out of sync
16103 * with GNU Chess's clock -- but then, referees are always right.
16106 static TimeMark tickStartTM;
16107 static long intendedTickLength;
16110 NextTickLength (long timeRemaining)
16112 long nominalTickLength, nextTickLength;
16114 if (timeRemaining > 0L && timeRemaining <= 10000L)
16115 nominalTickLength = 100L;
16117 nominalTickLength = 1000L;
16118 nextTickLength = timeRemaining % nominalTickLength;
16119 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16121 return nextTickLength;
16124 /* Adjust clock one minute up or down */
16126 AdjustClock (Boolean which, int dir)
16128 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16129 if(which) blackTimeRemaining += 60000*dir;
16130 else whiteTimeRemaining += 60000*dir;
16131 DisplayBothClocks();
16132 adjustedClock = TRUE;
16135 /* Stop clocks and reset to a fresh time control */
16139 (void) StopClockTimer();
16140 if (appData.icsActive) {
16141 whiteTimeRemaining = blackTimeRemaining = 0;
16142 } else if (searchTime) {
16143 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16144 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16145 } else { /* [HGM] correct new time quote for time odds */
16146 whiteTC = blackTC = fullTimeControlString;
16147 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16148 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16150 if (whiteFlag || blackFlag) {
16152 whiteFlag = blackFlag = FALSE;
16154 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16155 DisplayBothClocks();
16156 adjustedClock = FALSE;
16159 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16161 /* Decrement running clock by amount of time that has passed */
16165 long timeRemaining;
16166 long lastTickLength, fudge;
16169 if (!appData.clockMode) return;
16170 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16174 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16176 /* Fudge if we woke up a little too soon */
16177 fudge = intendedTickLength - lastTickLength;
16178 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16180 if (WhiteOnMove(forwardMostMove)) {
16181 if(whiteNPS >= 0) lastTickLength = 0;
16182 timeRemaining = whiteTimeRemaining -= lastTickLength;
16183 if(timeRemaining < 0 && !appData.icsActive) {
16184 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16185 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16186 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16187 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16190 DisplayWhiteClock(whiteTimeRemaining - fudge,
16191 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16193 if(blackNPS >= 0) lastTickLength = 0;
16194 timeRemaining = blackTimeRemaining -= lastTickLength;
16195 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16196 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16198 blackStartMove = forwardMostMove;
16199 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16202 DisplayBlackClock(blackTimeRemaining - fudge,
16203 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16205 if (CheckFlags()) return;
16208 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16209 StartClockTimer(intendedTickLength);
16211 /* if the time remaining has fallen below the alarm threshold, sound the
16212 * alarm. if the alarm has sounded and (due to a takeback or time control
16213 * with increment) the time remaining has increased to a level above the
16214 * threshold, reset the alarm so it can sound again.
16217 if (appData.icsActive && appData.icsAlarm) {
16219 /* make sure we are dealing with the user's clock */
16220 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16221 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16224 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16225 alarmSounded = FALSE;
16226 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16228 alarmSounded = TRUE;
16234 /* A player has just moved, so stop the previously running
16235 clock and (if in clock mode) start the other one.
16236 We redisplay both clocks in case we're in ICS mode, because
16237 ICS gives us an update to both clocks after every move.
16238 Note that this routine is called *after* forwardMostMove
16239 is updated, so the last fractional tick must be subtracted
16240 from the color that is *not* on move now.
16243 SwitchClocks (int newMoveNr)
16245 long lastTickLength;
16247 int flagged = FALSE;
16251 if (StopClockTimer() && appData.clockMode) {
16252 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16253 if (!WhiteOnMove(forwardMostMove)) {
16254 if(blackNPS >= 0) lastTickLength = 0;
16255 blackTimeRemaining -= lastTickLength;
16256 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16257 // if(pvInfoList[forwardMostMove].time == -1)
16258 pvInfoList[forwardMostMove].time = // use GUI time
16259 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16261 if(whiteNPS >= 0) lastTickLength = 0;
16262 whiteTimeRemaining -= lastTickLength;
16263 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16264 // if(pvInfoList[forwardMostMove].time == -1)
16265 pvInfoList[forwardMostMove].time =
16266 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16268 flagged = CheckFlags();
16270 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16271 CheckTimeControl();
16273 if (flagged || !appData.clockMode) return;
16275 switch (gameMode) {
16276 case MachinePlaysBlack:
16277 case MachinePlaysWhite:
16278 case BeginningOfGame:
16279 if (pausing) return;
16283 case PlayFromGameFile:
16291 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16292 if(WhiteOnMove(forwardMostMove))
16293 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16294 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16298 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16299 whiteTimeRemaining : blackTimeRemaining);
16300 StartClockTimer(intendedTickLength);
16304 /* Stop both clocks */
16308 long lastTickLength;
16311 if (!StopClockTimer()) return;
16312 if (!appData.clockMode) return;
16316 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16317 if (WhiteOnMove(forwardMostMove)) {
16318 if(whiteNPS >= 0) lastTickLength = 0;
16319 whiteTimeRemaining -= lastTickLength;
16320 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16322 if(blackNPS >= 0) lastTickLength = 0;
16323 blackTimeRemaining -= lastTickLength;
16324 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16329 /* Start clock of player on move. Time may have been reset, so
16330 if clock is already running, stop and restart it. */
16334 (void) StopClockTimer(); /* in case it was running already */
16335 DisplayBothClocks();
16336 if (CheckFlags()) return;
16338 if (!appData.clockMode) return;
16339 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16341 GetTimeMark(&tickStartTM);
16342 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16343 whiteTimeRemaining : blackTimeRemaining);
16345 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16346 whiteNPS = blackNPS = -1;
16347 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16348 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16349 whiteNPS = first.nps;
16350 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16351 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16352 blackNPS = first.nps;
16353 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16354 whiteNPS = second.nps;
16355 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16356 blackNPS = second.nps;
16357 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16359 StartClockTimer(intendedTickLength);
16363 TimeString (long ms)
16365 long second, minute, hour, day;
16367 static char buf[32];
16369 if (ms > 0 && ms <= 9900) {
16370 /* convert milliseconds to tenths, rounding up */
16371 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16373 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16377 /* convert milliseconds to seconds, rounding up */
16378 /* use floating point to avoid strangeness of integer division
16379 with negative dividends on many machines */
16380 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16387 day = second / (60 * 60 * 24);
16388 second = second % (60 * 60 * 24);
16389 hour = second / (60 * 60);
16390 second = second % (60 * 60);
16391 minute = second / 60;
16392 second = second % 60;
16395 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16396 sign, day, hour, minute, second);
16398 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16400 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16407 * This is necessary because some C libraries aren't ANSI C compliant yet.
16410 StrStr (char *string, char *match)
16414 length = strlen(match);
16416 for (i = strlen(string) - length; i >= 0; i--, string++)
16417 if (!strncmp(match, string, length))
16424 StrCaseStr (char *string, char *match)
16428 length = strlen(match);
16430 for (i = strlen(string) - length; i >= 0; i--, string++) {
16431 for (j = 0; j < length; j++) {
16432 if (ToLower(match[j]) != ToLower(string[j]))
16435 if (j == length) return string;
16443 StrCaseCmp (char *s1, char *s2)
16448 c1 = ToLower(*s1++);
16449 c2 = ToLower(*s2++);
16450 if (c1 > c2) return 1;
16451 if (c1 < c2) return -1;
16452 if (c1 == NULLCHAR) return 0;
16460 return isupper(c) ? tolower(c) : c;
16467 return islower(c) ? toupper(c) : c;
16469 #endif /* !_amigados */
16476 if ((ret = (char *) malloc(strlen(s) + 1)))
16478 safeStrCpy(ret, s, strlen(s)+1);
16484 StrSavePtr (char *s, char **savePtr)
16489 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16490 safeStrCpy(*savePtr, s, strlen(s)+1);
16502 clock = time((time_t *)NULL);
16503 tm = localtime(&clock);
16504 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16505 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16506 return StrSave(buf);
16511 PositionToFEN (int move, char *overrideCastling)
16513 int i, j, fromX, fromY, toX, toY;
16520 whiteToPlay = (gameMode == EditPosition) ?
16521 !blackPlaysFirst : (move % 2 == 0);
16524 /* Piece placement data */
16525 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16526 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16528 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16529 if (boards[move][i][j] == EmptySquare) {
16531 } else { ChessSquare piece = boards[move][i][j];
16532 if (emptycount > 0) {
16533 if(emptycount<10) /* [HGM] can be >= 10 */
16534 *p++ = '0' + emptycount;
16535 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16538 if(PieceToChar(piece) == '+') {
16539 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16541 piece = (ChessSquare)(DEMOTED piece);
16543 *p++ = PieceToChar(piece);
16545 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16546 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16551 if (emptycount > 0) {
16552 if(emptycount<10) /* [HGM] can be >= 10 */
16553 *p++ = '0' + emptycount;
16554 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16561 /* [HGM] print Crazyhouse or Shogi holdings */
16562 if( gameInfo.holdingsWidth ) {
16563 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16565 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16566 piece = boards[move][i][BOARD_WIDTH-1];
16567 if( piece != EmptySquare )
16568 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16569 *p++ = PieceToChar(piece);
16571 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16572 piece = boards[move][BOARD_HEIGHT-i-1][0];
16573 if( piece != EmptySquare )
16574 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16575 *p++ = PieceToChar(piece);
16578 if( q == p ) *p++ = '-';
16584 *p++ = whiteToPlay ? 'w' : 'b';
16587 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16588 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16590 if(nrCastlingRights) {
16592 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16593 /* [HGM] write directly from rights */
16594 if(boards[move][CASTLING][2] != NoRights &&
16595 boards[move][CASTLING][0] != NoRights )
16596 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16597 if(boards[move][CASTLING][2] != NoRights &&
16598 boards[move][CASTLING][1] != NoRights )
16599 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16600 if(boards[move][CASTLING][5] != NoRights &&
16601 boards[move][CASTLING][3] != NoRights )
16602 *p++ = boards[move][CASTLING][3] + AAA;
16603 if(boards[move][CASTLING][5] != NoRights &&
16604 boards[move][CASTLING][4] != NoRights )
16605 *p++ = boards[move][CASTLING][4] + AAA;
16608 /* [HGM] write true castling rights */
16609 if( nrCastlingRights == 6 ) {
16610 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16611 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16612 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16613 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16614 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16615 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16616 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16617 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16620 if (q == p) *p++ = '-'; /* No castling rights */
16624 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16625 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16626 /* En passant target square */
16627 if (move > backwardMostMove) {
16628 fromX = moveList[move - 1][0] - AAA;
16629 fromY = moveList[move - 1][1] - ONE;
16630 toX = moveList[move - 1][2] - AAA;
16631 toY = moveList[move - 1][3] - ONE;
16632 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16633 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16634 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16636 /* 2-square pawn move just happened */
16638 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16642 } else if(move == backwardMostMove) {
16643 // [HGM] perhaps we should always do it like this, and forget the above?
16644 if((signed char)boards[move][EP_STATUS] >= 0) {
16645 *p++ = boards[move][EP_STATUS] + AAA;
16646 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16657 /* [HGM] find reversible plies */
16658 { int i = 0, j=move;
16660 if (appData.debugMode) { int k;
16661 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16662 for(k=backwardMostMove; k<=forwardMostMove; k++)
16663 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16667 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16668 if( j == backwardMostMove ) i += initialRulePlies;
16669 sprintf(p, "%d ", i);
16670 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16672 /* Fullmove number */
16673 sprintf(p, "%d", (move / 2) + 1);
16675 return StrSave(buf);
16679 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16688 /* [HGM] by default clear Crazyhouse holdings, if present */
16689 if(gameInfo.holdingsWidth) {
16690 for(i=0; i<BOARD_HEIGHT; i++) {
16691 board[i][0] = EmptySquare; /* black holdings */
16692 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16693 board[i][1] = (ChessSquare) 0; /* black counts */
16694 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16698 /* Piece placement data */
16699 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16702 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16703 if (*p == '/') p++;
16704 emptycount = gameInfo.boardWidth - j;
16705 while (emptycount--)
16706 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16708 #if(BOARD_FILES >= 10)
16709 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16710 p++; emptycount=10;
16711 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16712 while (emptycount--)
16713 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16715 } else if (isdigit(*p)) {
16716 emptycount = *p++ - '0';
16717 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16718 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16719 while (emptycount--)
16720 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16721 } else if (*p == '+' || isalpha(*p)) {
16722 if (j >= gameInfo.boardWidth) return FALSE;
16724 piece = CharToPiece(*++p);
16725 if(piece == EmptySquare) return FALSE; /* unknown piece */
16726 piece = (ChessSquare) (PROMOTED piece ); p++;
16727 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16728 } else piece = CharToPiece(*p++);
16730 if(piece==EmptySquare) return FALSE; /* unknown piece */
16731 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16732 piece = (ChessSquare) (PROMOTED piece);
16733 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16736 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16742 while (*p == '/' || *p == ' ') p++;
16744 /* [HGM] look for Crazyhouse holdings here */
16745 while(*p==' ') p++;
16746 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16748 if(*p == '-' ) p++; /* empty holdings */ else {
16749 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16750 /* if we would allow FEN reading to set board size, we would */
16751 /* have to add holdings and shift the board read so far here */
16752 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16754 if((int) piece >= (int) BlackPawn ) {
16755 i = (int)piece - (int)BlackPawn;
16756 i = PieceToNumber((ChessSquare)i);
16757 if( i >= gameInfo.holdingsSize ) return FALSE;
16758 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16759 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16761 i = (int)piece - (int)WhitePawn;
16762 i = PieceToNumber((ChessSquare)i);
16763 if( i >= gameInfo.holdingsSize ) return FALSE;
16764 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16765 board[i][BOARD_WIDTH-2]++; /* black holdings */
16772 while(*p == ' ') p++;
16776 if(appData.colorNickNames) {
16777 if( c == appData.colorNickNames[0] ) c = 'w'; else
16778 if( c == appData.colorNickNames[1] ) c = 'b';
16782 *blackPlaysFirst = FALSE;
16785 *blackPlaysFirst = TRUE;
16791 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16792 /* return the extra info in global variiables */
16794 /* set defaults in case FEN is incomplete */
16795 board[EP_STATUS] = EP_UNKNOWN;
16796 for(i=0; i<nrCastlingRights; i++ ) {
16797 board[CASTLING][i] =
16798 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16799 } /* assume possible unless obviously impossible */
16800 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16801 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16802 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16803 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16804 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16805 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16806 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16807 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16810 while(*p==' ') p++;
16811 if(nrCastlingRights) {
16812 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16813 /* castling indicator present, so default becomes no castlings */
16814 for(i=0; i<nrCastlingRights; i++ ) {
16815 board[CASTLING][i] = NoRights;
16818 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16819 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16820 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16821 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16822 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16824 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16825 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16826 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16828 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16829 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16830 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16831 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16832 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16833 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16836 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16837 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16838 board[CASTLING][2] = whiteKingFile;
16841 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16842 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16843 board[CASTLING][2] = whiteKingFile;
16846 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16847 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16848 board[CASTLING][5] = blackKingFile;
16851 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16852 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16853 board[CASTLING][5] = blackKingFile;
16856 default: /* FRC castlings */
16857 if(c >= 'a') { /* black rights */
16858 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16859 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16860 if(i == BOARD_RGHT) break;
16861 board[CASTLING][5] = i;
16863 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16864 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16866 board[CASTLING][3] = c;
16868 board[CASTLING][4] = c;
16869 } else { /* white rights */
16870 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16871 if(board[0][i] == WhiteKing) break;
16872 if(i == BOARD_RGHT) break;
16873 board[CASTLING][2] = i;
16874 c -= AAA - 'a' + 'A';
16875 if(board[0][c] >= WhiteKing) break;
16877 board[CASTLING][0] = c;
16879 board[CASTLING][1] = c;
16883 for(i=0; i<nrCastlingRights; i++)
16884 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16885 if (appData.debugMode) {
16886 fprintf(debugFP, "FEN castling rights:");
16887 for(i=0; i<nrCastlingRights; i++)
16888 fprintf(debugFP, " %d", board[CASTLING][i]);
16889 fprintf(debugFP, "\n");
16892 while(*p==' ') p++;
16895 /* read e.p. field in games that know e.p. capture */
16896 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16897 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16899 p++; board[EP_STATUS] = EP_NONE;
16901 char c = *p++ - AAA;
16903 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16904 if(*p >= '0' && *p <='9') p++;
16905 board[EP_STATUS] = c;
16910 if(sscanf(p, "%d", &i) == 1) {
16911 FENrulePlies = i; /* 50-move ply counter */
16912 /* (The move number is still ignored) */
16919 EditPositionPasteFEN (char *fen)
16922 Board initial_position;
16924 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16925 DisplayError(_("Bad FEN position in clipboard"), 0);
16928 int savedBlackPlaysFirst = blackPlaysFirst;
16929 EditPositionEvent();
16930 blackPlaysFirst = savedBlackPlaysFirst;
16931 CopyBoard(boards[0], initial_position);
16932 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16933 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16934 DisplayBothClocks();
16935 DrawPosition(FALSE, boards[currentMove]);
16940 static char cseq[12] = "\\ ";
16943 set_cont_sequence (char *new_seq)
16948 // handle bad attempts to set the sequence
16950 return 0; // acceptable error - no debug
16952 len = strlen(new_seq);
16953 ret = (len > 0) && (len < sizeof(cseq));
16955 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16956 else if (appData.debugMode)
16957 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16962 reformat a source message so words don't cross the width boundary. internal
16963 newlines are not removed. returns the wrapped size (no null character unless
16964 included in source message). If dest is NULL, only calculate the size required
16965 for the dest buffer. lp argument indicats line position upon entry, and it's
16966 passed back upon exit.
16969 wrap (char *dest, char *src, int count, int width, int *lp)
16971 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16973 cseq_len = strlen(cseq);
16974 old_line = line = *lp;
16975 ansi = len = clen = 0;
16977 for (i=0; i < count; i++)
16979 if (src[i] == '\033')
16982 // if we hit the width, back up
16983 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16985 // store i & len in case the word is too long
16986 old_i = i, old_len = len;
16988 // find the end of the last word
16989 while (i && src[i] != ' ' && src[i] != '\n')
16995 // word too long? restore i & len before splitting it
16996 if ((old_i-i+clen) >= width)
17003 if (i && src[i-1] == ' ')
17006 if (src[i] != ' ' && src[i] != '\n')
17013 // now append the newline and continuation sequence
17018 strncpy(dest+len, cseq, cseq_len);
17026 dest[len] = src[i];
17030 if (src[i] == '\n')
17035 if (dest && appData.debugMode)
17037 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17038 count, width, line, len, *lp);
17039 show_bytes(debugFP, src, count);
17040 fprintf(debugFP, "\ndest: ");
17041 show_bytes(debugFP, dest, len);
17042 fprintf(debugFP, "\n");
17044 *lp = dest ? line : old_line;
17049 // [HGM] vari: routines for shelving variations
17050 Boolean modeRestore = FALSE;
17053 PushInner (int firstMove, int lastMove)
17055 int i, j, nrMoves = lastMove - firstMove;
17057 // push current tail of game on stack
17058 savedResult[storedGames] = gameInfo.result;
17059 savedDetails[storedGames] = gameInfo.resultDetails;
17060 gameInfo.resultDetails = NULL;
17061 savedFirst[storedGames] = firstMove;
17062 savedLast [storedGames] = lastMove;
17063 savedFramePtr[storedGames] = framePtr;
17064 framePtr -= nrMoves; // reserve space for the boards
17065 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17066 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17067 for(j=0; j<MOVE_LEN; j++)
17068 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17069 for(j=0; j<2*MOVE_LEN; j++)
17070 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17071 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17072 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17073 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17074 pvInfoList[firstMove+i-1].depth = 0;
17075 commentList[framePtr+i] = commentList[firstMove+i];
17076 commentList[firstMove+i] = NULL;
17080 forwardMostMove = firstMove; // truncate game so we can start variation
17084 PushTail (int firstMove, int lastMove)
17086 if(appData.icsActive) { // only in local mode
17087 forwardMostMove = currentMove; // mimic old ICS behavior
17090 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17092 PushInner(firstMove, lastMove);
17093 if(storedGames == 1) GreyRevert(FALSE);
17094 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17098 PopInner (Boolean annotate)
17101 char buf[8000], moveBuf[20];
17103 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17104 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17105 nrMoves = savedLast[storedGames] - currentMove;
17108 if(!WhiteOnMove(currentMove))
17109 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17110 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17111 for(i=currentMove; i<forwardMostMove; i++) {
17113 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17114 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17115 strcat(buf, moveBuf);
17116 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17117 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17121 for(i=1; i<=nrMoves; i++) { // copy last variation back
17122 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17123 for(j=0; j<MOVE_LEN; j++)
17124 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17125 for(j=0; j<2*MOVE_LEN; j++)
17126 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17127 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17128 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17129 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17130 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17131 commentList[currentMove+i] = commentList[framePtr+i];
17132 commentList[framePtr+i] = NULL;
17134 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17135 framePtr = savedFramePtr[storedGames];
17136 gameInfo.result = savedResult[storedGames];
17137 if(gameInfo.resultDetails != NULL) {
17138 free(gameInfo.resultDetails);
17140 gameInfo.resultDetails = savedDetails[storedGames];
17141 forwardMostMove = currentMove + nrMoves;
17145 PopTail (Boolean annotate)
17147 if(appData.icsActive) return FALSE; // only in local mode
17148 if(!storedGames) return FALSE; // sanity
17149 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17151 PopInner(annotate);
17152 if(currentMove < forwardMostMove) ForwardEvent(); else
17153 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17155 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17161 { // remove all shelved variations
17163 for(i=0; i<storedGames; i++) {
17164 if(savedDetails[i])
17165 free(savedDetails[i]);
17166 savedDetails[i] = NULL;
17168 for(i=framePtr; i<MAX_MOVES; i++) {
17169 if(commentList[i]) free(commentList[i]);
17170 commentList[i] = NULL;
17172 framePtr = MAX_MOVES-1;
17177 LoadVariation (int index, char *text)
17178 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17179 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17180 int level = 0, move;
17182 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17183 // first find outermost bracketing variation
17184 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17185 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17186 if(*p == '{') wait = '}'; else
17187 if(*p == '[') wait = ']'; else
17188 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17189 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17191 if(*p == wait) wait = NULLCHAR; // closing ]} found
17194 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17195 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17196 end[1] = NULLCHAR; // clip off comment beyond variation
17197 ToNrEvent(currentMove-1);
17198 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17199 // kludge: use ParsePV() to append variation to game
17200 move = currentMove;
17201 ParsePV(start, TRUE, TRUE);
17202 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17203 ClearPremoveHighlights();
17205 ToNrEvent(currentMove+1);