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(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second, pairing;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
467 /* animateTraining preserves the state of appData.animate
468 * when Training mode is activated. This allows the
469 * response to be animated when appData.animate == TRUE and
470 * appData.animateDragging == TRUE.
472 Boolean animateTraining;
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char initialRights[BOARD_FILES];
482 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int initialRulePlies, FENrulePlies;
484 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
505 ChessSquare FIDEArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackBishop, BlackKnight, BlackRook }
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackKing, BlackKnight, BlackRook }
519 ChessSquare KnightmateArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522 { BlackRook, BlackMan, BlackBishop, BlackQueen,
523 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackMan, BlackFerz,
551 BlackKing, BlackMan, BlackKnight, BlackRook }
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 #define GothicArray CapablancaArray
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 #define FalconArray CapablancaArray
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
639 Board initialPosition;
642 /* Convert str to a rating. Checks for special cases of "----",
644 "++++", etc. Also strips ()'s */
646 string_to_rating (char *str)
648 while(*str && !isdigit(*str)) ++str;
650 return 0; /* One of the special "no rating" cases */
658 /* Init programStats */
659 programStats.movelist[0] = 0;
660 programStats.depth = 0;
661 programStats.nr_moves = 0;
662 programStats.moves_left = 0;
663 programStats.nodes = 0;
664 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
665 programStats.score = 0;
666 programStats.got_only_move = 0;
667 programStats.got_fail = 0;
668 programStats.line_is_book = 0;
673 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674 if (appData.firstPlaysBlack) {
675 first.twoMachinesColor = "black\n";
676 second.twoMachinesColor = "white\n";
678 first.twoMachinesColor = "white\n";
679 second.twoMachinesColor = "black\n";
682 first.other = &second;
683 second.other = &first;
686 if(appData.timeOddsMode) {
687 norm = appData.timeOdds[0];
688 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690 first.timeOdds = appData.timeOdds[0]/norm;
691 second.timeOdds = appData.timeOdds[1]/norm;
694 if(programVersion) free(programVersion);
695 if (appData.noChessProgram) {
696 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697 sprintf(programVersion, "%s", PACKAGE_STRING);
699 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706 UnloadEngine (ChessProgramState *cps)
708 /* Kill off first chess program */
709 if (cps->isr != NULL)
710 RemoveInputSource(cps->isr);
713 if (cps->pr != NoProc) {
715 DoSleep( appData.delayBeforeQuit );
716 SendToProgram("quit\n", cps);
717 DoSleep( appData.delayAfterQuit );
718 DestroyChildProcess(cps->pr, cps->useSigterm);
721 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 ClearOptions (ChessProgramState *cps)
728 cps->nrOptions = cps->comboCnt = 0;
729 for(i=0; i<MAX_OPTIONS; i++) {
730 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731 cps->option[i].textValue = 0;
735 char *engineNames[] = {
741 InitEngine (ChessProgramState *cps, int n)
742 { // [HGM] all engine initialiation put in a function that does one engine
746 cps->which = engineNames[n];
747 cps->maybeThinking = FALSE;
751 cps->sendDrawOffers = 1;
753 cps->program = appData.chessProgram[n];
754 cps->host = appData.host[n];
755 cps->dir = appData.directory[n];
756 cps->initString = appData.engInitString[n];
757 cps->computerString = appData.computerString[n];
758 cps->useSigint = TRUE;
759 cps->useSigterm = TRUE;
760 cps->reuse = appData.reuse[n];
761 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
762 cps->useSetboard = FALSE;
764 cps->usePing = FALSE;
767 cps->usePlayother = FALSE;
768 cps->useColors = TRUE;
769 cps->useUsermove = FALSE;
770 cps->sendICS = FALSE;
771 cps->sendName = appData.icsActive;
772 cps->sdKludge = FALSE;
773 cps->stKludge = FALSE;
774 TidyProgramName(cps->program, cps->host, cps->tidy);
776 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777 cps->analysisSupport = 2; /* detect */
778 cps->analyzing = FALSE;
779 cps->initDone = FALSE;
781 /* New features added by Tord: */
782 cps->useFEN960 = FALSE;
783 cps->useOOCastle = TRUE;
784 /* End of new features added by Tord. */
785 cps->fenOverride = appData.fenOverride[n];
787 /* [HGM] time odds: set factor for each machine */
788 cps->timeOdds = appData.timeOdds[n];
790 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791 cps->accumulateTC = appData.accumulateTC[n];
792 cps->maxNrOfSessions = 1;
797 cps->supportsNPS = UNKNOWN;
798 cps->memSize = FALSE;
799 cps->maxCores = FALSE;
800 cps->egtFormats[0] = NULLCHAR;
803 cps->optionSettings = appData.engOptions[n];
805 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806 cps->isUCI = appData.isUCI[n]; /* [AS] */
807 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809 if (appData.protocolVersion[n] > PROTOVER
810 || appData.protocolVersion[n] < 1)
815 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816 appData.protocolVersion[n]);
817 if( (len >= MSG_SIZ) && appData.debugMode )
818 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820 DisplayFatalError(buf, 0, 2);
824 cps->protocolVersion = appData.protocolVersion[n];
827 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
828 ParseFeatures(appData.featureDefaults, cps);
831 ChessProgramState *savCps;
837 if(WaitForEngine(savCps, LoadEngine)) return;
838 CommonEngineInit(); // recalculate time odds
839 if(gameInfo.variant != StringToVariant(appData.variant)) {
840 // we changed variant when loading the engine; this forces us to reset
841 Reset(TRUE, savCps != &first);
842 EditGameEvent(); // for consistency with other path, as Reset changes mode
844 InitChessProgram(savCps, FALSE);
845 SendToProgram("force\n", savCps);
846 DisplayMessage("", "");
847 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854 ReplaceEngine (ChessProgramState *cps, int n)
858 appData.noChessProgram = FALSE;
859 appData.clockMode = TRUE;
862 if(n) return; // only startup first engine immediately; second can wait
863 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870 static char resetOptions[] =
871 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876 Load (ChessProgramState *cps, int i)
878 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883 appData.firstProtocolVersion = PROTOVER;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
900 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901 snprintf(command, MSG_SIZ, "%s %s", p, params);
904 appData.chessProgram[i] = strdup(p);
905 appData.isUCI[i] = isUCI;
906 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907 appData.hasOwnBookUCI[i] = hasBook;
908 if(!nickName[0]) useNick = FALSE;
909 if(useNick) ASSIGN(appData.pgnName[i], nickName);
913 q = firstChessProgramNames;
914 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917 quote, p, quote, appData.directory[i],
918 useNick ? " -fn \"" : "",
919 useNick ? nickName : "",
921 v1 ? " -firstProtocolVersion 1" : "",
922 hasBook ? "" : " -fNoOwnBookUCI",
923 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924 storeVariant ? " -variant " : "",
925 storeVariant ? VariantName(gameInfo.variant) : "");
926 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930 ReplaceEngine(cps, i);
936 int matched, min, sec;
938 * Parse timeControl resource
940 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941 appData.movesPerSession)) {
943 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944 DisplayFatalError(buf, 0, 2);
948 * Parse searchTime resource
950 if (*appData.searchTime != NULLCHAR) {
951 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953 searchTime = min * 60;
954 } else if (matched == 2) {
955 searchTime = min * 60 + sec;
958 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959 DisplayFatalError(buf, 0, 2);
968 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971 GetTimeMark(&programStartTime);
972 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973 appData.seedBase = random() + (random()<<15);
974 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
977 programStats.ok_to_send = 1;
978 programStats.seen_stat = 0;
981 * Initialize game list
987 * Internet chess server status
989 if (appData.icsActive) {
990 appData.matchMode = FALSE;
991 appData.matchGames = 0;
993 appData.noChessProgram = !appData.zippyPlay;
995 appData.zippyPlay = FALSE;
996 appData.zippyTalk = FALSE;
997 appData.noChessProgram = TRUE;
999 if (*appData.icsHelper != NULLCHAR) {
1000 appData.useTelnet = TRUE;
1001 appData.telnetProgram = appData.icsHelper;
1004 appData.zippyTalk = appData.zippyPlay = FALSE;
1007 /* [AS] Initialize pv info list [HGM] and game state */
1011 for( i=0; i<=framePtr; i++ ) {
1012 pvInfoList[i].depth = -1;
1013 boards[i][EP_STATUS] = EP_NONE;
1014 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1020 /* [AS] Adjudication threshold */
1021 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023 InitEngine(&first, 0);
1024 InitEngine(&second, 1);
1027 pairing.which = "pairing"; // pairing engine
1028 pairing.pr = NoProc;
1030 pairing.program = appData.pairingEngine;
1031 pairing.host = "localhost";
1034 if (appData.icsActive) {
1035 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1036 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037 appData.clockMode = FALSE;
1038 first.sendTime = second.sendTime = 0;
1042 /* Override some settings from environment variables, for backward
1043 compatibility. Unfortunately it's not feasible to have the env
1044 vars just set defaults, at least in xboard. Ugh.
1046 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1051 if (!appData.icsActive) {
1055 /* Check for variants that are supported only in ICS mode,
1056 or not at all. Some that are accepted here nevertheless
1057 have bugs; see comments below.
1059 VariantClass variant = StringToVariant(appData.variant);
1061 case VariantBughouse: /* need four players and two boards */
1062 case VariantKriegspiel: /* need to hide pieces and move details */
1063 /* case VariantFischeRandom: (Fabien: moved below) */
1064 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065 if( (len >= MSG_SIZ) && appData.debugMode )
1066 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068 DisplayFatalError(buf, 0, 2);
1071 case VariantUnknown:
1072 case VariantLoadable:
1082 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083 if( (len >= MSG_SIZ) && appData.debugMode )
1084 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086 DisplayFatalError(buf, 0, 2);
1089 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1090 case VariantFairy: /* [HGM] TestLegality definitely off! */
1091 case VariantGothic: /* [HGM] should work */
1092 case VariantCapablanca: /* [HGM] should work */
1093 case VariantCourier: /* [HGM] initial forced moves not implemented */
1094 case VariantShogi: /* [HGM] could still mate with pawn drop */
1095 case VariantKnightmate: /* [HGM] should work */
1096 case VariantCylinder: /* [HGM] untested */
1097 case VariantFalcon: /* [HGM] untested */
1098 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099 offboard interposition not understood */
1100 case VariantNormal: /* definitely works! */
1101 case VariantWildCastle: /* pieces not automatically shuffled */
1102 case VariantNoCastle: /* pieces not automatically shuffled */
1103 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104 case VariantLosers: /* should work except for win condition,
1105 and doesn't know captures are mandatory */
1106 case VariantSuicide: /* should work except for win condition,
1107 and doesn't know captures are mandatory */
1108 case VariantGiveaway: /* should work except for win condition,
1109 and doesn't know captures are mandatory */
1110 case VariantTwoKings: /* should work */
1111 case VariantAtomic: /* should work except for win condition */
1112 case Variant3Check: /* should work except for win condition */
1113 case VariantShatranj: /* should work except for all win conditions */
1114 case VariantMakruk: /* should work except for draw countdown */
1115 case VariantBerolina: /* might work if TestLegality is off */
1116 case VariantCapaRandom: /* should work */
1117 case VariantJanus: /* should work */
1118 case VariantSuper: /* experimental */
1119 case VariantGreat: /* experimental, requires legality testing to be off */
1120 case VariantSChess: /* S-Chess, should work */
1121 case VariantGrand: /* should work */
1122 case VariantSpartan: /* should work */
1130 NextIntegerFromString (char ** str, long * value)
1135 while( *s == ' ' || *s == '\t' ) {
1141 if( *s >= '0' && *s <= '9' ) {
1142 while( *s >= '0' && *s <= '9' ) {
1143 *value = *value * 10 + (*s - '0');
1156 NextTimeControlFromString (char ** str, long * value)
1159 int result = NextIntegerFromString( str, &temp );
1162 *value = temp * 60; /* Minutes */
1163 if( **str == ':' ) {
1165 result = NextIntegerFromString( str, &temp );
1166 *value += temp; /* Seconds */
1174 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1175 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1176 int result = -1, type = 0; long temp, temp2;
1178 if(**str != ':') return -1; // old params remain in force!
1180 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1181 if( NextIntegerFromString( str, &temp ) ) return -1;
1182 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1185 /* time only: incremental or sudden-death time control */
1186 if(**str == '+') { /* increment follows; read it */
1188 if(**str == '!') type = *(*str)++; // Bronstein TC
1189 if(result = NextIntegerFromString( str, &temp2)) return -1;
1190 *inc = temp2 * 1000;
1191 if(**str == '.') { // read fraction of increment
1192 char *start = ++(*str);
1193 if(result = NextIntegerFromString( str, &temp2)) return -1;
1195 while(start++ < *str) temp2 /= 10;
1199 *moves = 0; *tc = temp * 1000; *incType = type;
1203 (*str)++; /* classical time control */
1204 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1216 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1217 { /* [HGM] get time to add from the multi-session time-control string */
1218 int incType, moves=1; /* kludge to force reading of first session */
1219 long time, increment;
1222 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1223 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1225 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1226 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1227 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1228 if(movenr == -1) return time; /* last move before new session */
1229 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1230 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1231 if(!moves) return increment; /* current session is incremental */
1232 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1233 } while(movenr >= -1); /* try again for next session */
1235 return 0; // no new time quota on this move
1239 ParseTimeControl (char *tc, float ti, int mps)
1243 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1246 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1247 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1248 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1252 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1254 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1257 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1259 snprintf(buf, MSG_SIZ, ":%s", mytc);
1261 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1263 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1268 /* Parse second time control */
1271 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279 timeControl_2 = tc2 * 1000;
1289 timeControl = tc1 * 1000;
1292 timeIncrement = ti * 1000; /* convert to ms */
1293 movesPerSession = 0;
1296 movesPerSession = mps;
1304 if (appData.debugMode) {
1305 fprintf(debugFP, "%s\n", programVersion);
1308 set_cont_sequence(appData.wrapContSeq);
1309 if (appData.matchGames > 0) {
1310 appData.matchMode = TRUE;
1311 } else if (appData.matchMode) {
1312 appData.matchGames = 1;
1314 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1315 appData.matchGames = appData.sameColorGames;
1316 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1317 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1318 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1321 if (appData.noChessProgram || first.protocolVersion == 1) {
1324 /* kludge: allow timeout for initial "feature" commands */
1326 DisplayMessage("", _("Starting chess program"));
1327 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1332 CalculateIndex (int index, int gameNr)
1333 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1335 if(index > 0) return index; // fixed nmber
1336 if(index == 0) return 1;
1337 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1338 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1343 LoadGameOrPosition (int gameNr)
1344 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1345 if (*appData.loadGameFile != NULLCHAR) {
1346 if (!LoadGameFromFile(appData.loadGameFile,
1347 CalculateIndex(appData.loadGameIndex, gameNr),
1348 appData.loadGameFile, FALSE)) {
1349 DisplayFatalError(_("Bad game file"), 0, 1);
1352 } else if (*appData.loadPositionFile != NULLCHAR) {
1353 if (!LoadPositionFromFile(appData.loadPositionFile,
1354 CalculateIndex(appData.loadPositionIndex, gameNr),
1355 appData.loadPositionFile)) {
1356 DisplayFatalError(_("Bad position file"), 0, 1);
1364 ReserveGame (int gameNr, char resChar)
1366 FILE *tf = fopen(appData.tourneyFile, "r+");
1367 char *p, *q, c, buf[MSG_SIZ];
1368 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1369 safeStrCpy(buf, lastMsg, MSG_SIZ);
1370 DisplayMessage(_("Pick new game"), "");
1371 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1372 ParseArgsFromFile(tf);
1373 p = q = appData.results;
1374 if(appData.debugMode) {
1375 char *r = appData.participants;
1376 fprintf(debugFP, "results = '%s'\n", p);
1377 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1378 fprintf(debugFP, "\n");
1380 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1382 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1383 safeStrCpy(q, p, strlen(p) + 2);
1384 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1385 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1386 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1387 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1390 fseek(tf, -(strlen(p)+4), SEEK_END);
1392 if(c != '"') // depending on DOS or Unix line endings we can be one off
1393 fseek(tf, -(strlen(p)+2), SEEK_END);
1394 else fseek(tf, -(strlen(p)+3), SEEK_END);
1395 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1396 DisplayMessage(buf, "");
1397 free(p); appData.results = q;
1398 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1399 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1400 UnloadEngine(&first); // next game belongs to other pairing;
1401 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1406 MatchEvent (int mode)
1407 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1409 if(matchMode) { // already in match mode: switch it off
1411 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1414 // if(gameMode != BeginningOfGame) {
1415 // DisplayError(_("You can only start a match from the initial position."), 0);
1419 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1420 /* Set up machine vs. machine match */
1422 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1423 if(appData.tourneyFile[0]) {
1425 if(nextGame > appData.matchGames) {
1427 if(strchr(appData.results, '*') == NULL) {
1429 appData.tourneyCycles++;
1430 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1432 NextTourneyGame(-1, &dummy);
1434 if(nextGame <= appData.matchGames) {
1435 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1437 ScheduleDelayedEvent(NextMatchGame, 10000);
1442 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1443 DisplayError(buf, 0);
1444 appData.tourneyFile[0] = 0;
1448 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1449 DisplayFatalError(_("Can't have a match with no chess programs"),
1454 matchGame = roundNr = 1;
1455 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1460 InitBackEnd3 P((void))
1462 GameMode initialMode;
1466 InitChessProgram(&first, startedFromSetupPosition);
1468 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1469 free(programVersion);
1470 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1471 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1474 if (appData.icsActive) {
1476 /* [DM] Make a console window if needed [HGM] merged ifs */
1482 if (*appData.icsCommPort != NULLCHAR)
1483 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1484 appData.icsCommPort);
1486 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1487 appData.icsHost, appData.icsPort);
1489 if( (len >= MSG_SIZ) && appData.debugMode )
1490 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1492 DisplayFatalError(buf, err, 1);
1497 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1499 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1500 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1501 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1502 } else if (appData.noChessProgram) {
1508 if (*appData.cmailGameName != NULLCHAR) {
1510 OpenLoopback(&cmailPR);
1512 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1516 DisplayMessage("", "");
1517 if (StrCaseCmp(appData.initialMode, "") == 0) {
1518 initialMode = BeginningOfGame;
1519 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1520 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1521 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1522 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1525 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1526 initialMode = TwoMachinesPlay;
1527 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1528 initialMode = AnalyzeFile;
1529 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1530 initialMode = AnalyzeMode;
1531 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1532 initialMode = MachinePlaysWhite;
1533 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1534 initialMode = MachinePlaysBlack;
1535 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1536 initialMode = EditGame;
1537 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1538 initialMode = EditPosition;
1539 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1540 initialMode = Training;
1542 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1543 if( (len >= MSG_SIZ) && appData.debugMode )
1544 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1546 DisplayFatalError(buf, 0, 2);
1550 if (appData.matchMode) {
1551 if(appData.tourneyFile[0]) { // start tourney from command line
1553 if(f = fopen(appData.tourneyFile, "r")) {
1554 ParseArgsFromFile(f); // make sure tourney parmeters re known
1556 appData.clockMode = TRUE;
1558 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1561 } else if (*appData.cmailGameName != NULLCHAR) {
1562 /* Set up cmail mode */
1563 ReloadCmailMsgEvent(TRUE);
1565 /* Set up other modes */
1566 if (initialMode == AnalyzeFile) {
1567 if (*appData.loadGameFile == NULLCHAR) {
1568 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1572 if (*appData.loadGameFile != NULLCHAR) {
1573 (void) LoadGameFromFile(appData.loadGameFile,
1574 appData.loadGameIndex,
1575 appData.loadGameFile, TRUE);
1576 } else if (*appData.loadPositionFile != NULLCHAR) {
1577 (void) LoadPositionFromFile(appData.loadPositionFile,
1578 appData.loadPositionIndex,
1579 appData.loadPositionFile);
1580 /* [HGM] try to make self-starting even after FEN load */
1581 /* to allow automatic setup of fairy variants with wtm */
1582 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1583 gameMode = BeginningOfGame;
1584 setboardSpoiledMachineBlack = 1;
1586 /* [HGM] loadPos: make that every new game uses the setup */
1587 /* from file as long as we do not switch variant */
1588 if(!blackPlaysFirst) {
1589 startedFromPositionFile = TRUE;
1590 CopyBoard(filePosition, boards[0]);
1593 if (initialMode == AnalyzeMode) {
1594 if (appData.noChessProgram) {
1595 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1598 if (appData.icsActive) {
1599 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1603 } else if (initialMode == AnalyzeFile) {
1604 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1605 ShowThinkingEvent();
1607 AnalysisPeriodicEvent(1);
1608 } else if (initialMode == MachinePlaysWhite) {
1609 if (appData.noChessProgram) {
1610 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1614 if (appData.icsActive) {
1615 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1619 MachineWhiteEvent();
1620 } else if (initialMode == MachinePlaysBlack) {
1621 if (appData.noChessProgram) {
1622 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1626 if (appData.icsActive) {
1627 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1631 MachineBlackEvent();
1632 } else if (initialMode == TwoMachinesPlay) {
1633 if (appData.noChessProgram) {
1634 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1638 if (appData.icsActive) {
1639 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1644 } else if (initialMode == EditGame) {
1646 } else if (initialMode == EditPosition) {
1647 EditPositionEvent();
1648 } else if (initialMode == Training) {
1649 if (*appData.loadGameFile == NULLCHAR) {
1650 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1661 DisplayBook(current+1);
1663 MoveHistorySet( movelist, first, last, current, pvInfoList );
1665 EvalGraphSet( first, last, current, pvInfoList );
1667 MakeEngineOutputTitle();
1671 * Establish will establish a contact to a remote host.port.
1672 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1673 * used to talk to the host.
1674 * Returns 0 if okay, error code if not.
1681 if (*appData.icsCommPort != NULLCHAR) {
1682 /* Talk to the host through a serial comm port */
1683 return OpenCommPort(appData.icsCommPort, &icsPR);
1685 } else if (*appData.gateway != NULLCHAR) {
1686 if (*appData.remoteShell == NULLCHAR) {
1687 /* Use the rcmd protocol to run telnet program on a gateway host */
1688 snprintf(buf, sizeof(buf), "%s %s %s",
1689 appData.telnetProgram, appData.icsHost, appData.icsPort);
1690 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1693 /* Use the rsh program to run telnet program on a gateway host */
1694 if (*appData.remoteUser == NULLCHAR) {
1695 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1696 appData.gateway, appData.telnetProgram,
1697 appData.icsHost, appData.icsPort);
1699 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1700 appData.remoteShell, appData.gateway,
1701 appData.remoteUser, appData.telnetProgram,
1702 appData.icsHost, appData.icsPort);
1704 return StartChildProcess(buf, "", &icsPR);
1707 } else if (appData.useTelnet) {
1708 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1711 /* TCP socket interface differs somewhat between
1712 Unix and NT; handle details in the front end.
1714 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1719 EscapeExpand (char *p, char *q)
1720 { // [HGM] initstring: routine to shape up string arguments
1721 while(*p++ = *q++) if(p[-1] == '\\')
1723 case 'n': p[-1] = '\n'; break;
1724 case 'r': p[-1] = '\r'; break;
1725 case 't': p[-1] = '\t'; break;
1726 case '\\': p[-1] = '\\'; break;
1727 case 0: *p = 0; return;
1728 default: p[-1] = q[-1]; break;
1733 show_bytes (FILE *fp, char *buf, int count)
1736 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1737 fprintf(fp, "\\%03o", *buf & 0xff);
1746 /* Returns an errno value */
1748 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1750 char buf[8192], *p, *q, *buflim;
1751 int left, newcount, outcount;
1753 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1754 *appData.gateway != NULLCHAR) {
1755 if (appData.debugMode) {
1756 fprintf(debugFP, ">ICS: ");
1757 show_bytes(debugFP, message, count);
1758 fprintf(debugFP, "\n");
1760 return OutputToProcess(pr, message, count, outError);
1763 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1770 if (appData.debugMode) {
1771 fprintf(debugFP, ">ICS: ");
1772 show_bytes(debugFP, buf, newcount);
1773 fprintf(debugFP, "\n");
1775 outcount = OutputToProcess(pr, buf, newcount, outError);
1776 if (outcount < newcount) return -1; /* to be sure */
1783 } else if (((unsigned char) *p) == TN_IAC) {
1784 *q++ = (char) TN_IAC;
1791 if (appData.debugMode) {
1792 fprintf(debugFP, ">ICS: ");
1793 show_bytes(debugFP, buf, newcount);
1794 fprintf(debugFP, "\n");
1796 outcount = OutputToProcess(pr, buf, newcount, outError);
1797 if (outcount < newcount) return -1; /* to be sure */
1802 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1804 int outError, outCount;
1805 static int gotEof = 0;
1807 /* Pass data read from player on to ICS */
1810 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1811 if (outCount < count) {
1812 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1814 } else if (count < 0) {
1815 RemoveInputSource(isr);
1816 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1817 } else if (gotEof++ > 0) {
1818 RemoveInputSource(isr);
1819 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1825 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1826 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1827 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1828 SendToICS("date\n");
1829 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1832 /* added routine for printf style output to ics */
1834 ics_printf (char *format, ...)
1836 char buffer[MSG_SIZ];
1839 va_start(args, format);
1840 vsnprintf(buffer, sizeof(buffer), format, args);
1841 buffer[sizeof(buffer)-1] = '\0';
1849 int count, outCount, outError;
1851 if (icsPR == NoProc) return;
1854 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1855 if (outCount < count) {
1856 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1860 /* This is used for sending logon scripts to the ICS. Sending
1861 without a delay causes problems when using timestamp on ICC
1862 (at least on my machine). */
1864 SendToICSDelayed (char *s, long msdelay)
1866 int count, outCount, outError;
1868 if (icsPR == NoProc) return;
1871 if (appData.debugMode) {
1872 fprintf(debugFP, ">ICS: ");
1873 show_bytes(debugFP, s, count);
1874 fprintf(debugFP, "\n");
1876 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1878 if (outCount < count) {
1879 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 /* Remove all highlighting escape sequences in s
1885 Also deletes any suffix starting with '('
1888 StripHighlightAndTitle (char *s)
1890 static char retbuf[MSG_SIZ];
1893 while (*s != NULLCHAR) {
1894 while (*s == '\033') {
1895 while (*s != NULLCHAR && !isalpha(*s)) s++;
1896 if (*s != NULLCHAR) s++;
1898 while (*s != NULLCHAR && *s != '\033') {
1899 if (*s == '(' || *s == '[') {
1910 /* Remove all highlighting escape sequences in s */
1912 StripHighlight (char *s)
1914 static char retbuf[MSG_SIZ];
1917 while (*s != NULLCHAR) {
1918 while (*s == '\033') {
1919 while (*s != NULLCHAR && !isalpha(*s)) s++;
1920 if (*s != NULLCHAR) s++;
1922 while (*s != NULLCHAR && *s != '\033') {
1930 char *variantNames[] = VARIANT_NAMES;
1932 VariantName (VariantClass v)
1934 return variantNames[v];
1938 /* Identify a variant from the strings the chess servers use or the
1939 PGN Variant tag names we use. */
1941 StringToVariant (char *e)
1945 VariantClass v = VariantNormal;
1946 int i, found = FALSE;
1952 /* [HGM] skip over optional board-size prefixes */
1953 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1954 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1955 while( *e++ != '_');
1958 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1962 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1963 if (StrCaseStr(e, variantNames[i])) {
1964 v = (VariantClass) i;
1971 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1972 || StrCaseStr(e, "wild/fr")
1973 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1974 v = VariantFischeRandom;
1975 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1976 (i = 1, p = StrCaseStr(e, "w"))) {
1978 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1985 case 0: /* FICS only, actually */
1987 /* Castling legal even if K starts on d-file */
1988 v = VariantWildCastle;
1993 /* Castling illegal even if K & R happen to start in
1994 normal positions. */
1995 v = VariantNoCastle;
2008 /* Castling legal iff K & R start in normal positions */
2014 /* Special wilds for position setup; unclear what to do here */
2015 v = VariantLoadable;
2018 /* Bizarre ICC game */
2019 v = VariantTwoKings;
2022 v = VariantKriegspiel;
2028 v = VariantFischeRandom;
2031 v = VariantCrazyhouse;
2034 v = VariantBughouse;
2040 /* Not quite the same as FICS suicide! */
2041 v = VariantGiveaway;
2047 v = VariantShatranj;
2050 /* Temporary names for future ICC types. The name *will* change in
2051 the next xboard/WinBoard release after ICC defines it. */
2089 v = VariantCapablanca;
2092 v = VariantKnightmate;
2098 v = VariantCylinder;
2104 v = VariantCapaRandom;
2107 v = VariantBerolina;
2119 /* Found "wild" or "w" in the string but no number;
2120 must assume it's normal chess. */
2124 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2125 if( (len >= MSG_SIZ) && appData.debugMode )
2126 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2128 DisplayError(buf, 0);
2134 if (appData.debugMode) {
2135 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2136 e, wnum, VariantName(v));
2141 static int leftover_start = 0, leftover_len = 0;
2142 char star_match[STAR_MATCH_N][MSG_SIZ];
2144 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2145 advance *index beyond it, and set leftover_start to the new value of
2146 *index; else return FALSE. If pattern contains the character '*', it
2147 matches any sequence of characters not containing '\r', '\n', or the
2148 character following the '*' (if any), and the matched sequence(s) are
2149 copied into star_match.
2152 looking_at ( char *buf, int *index, char *pattern)
2154 char *bufp = &buf[*index], *patternp = pattern;
2156 char *matchp = star_match[0];
2159 if (*patternp == NULLCHAR) {
2160 *index = leftover_start = bufp - buf;
2164 if (*bufp == NULLCHAR) return FALSE;
2165 if (*patternp == '*') {
2166 if (*bufp == *(patternp + 1)) {
2168 matchp = star_match[++star_count];
2172 } else if (*bufp == '\n' || *bufp == '\r') {
2174 if (*patternp == NULLCHAR)
2179 *matchp++ = *bufp++;
2183 if (*patternp != *bufp) return FALSE;
2190 SendToPlayer (char *data, int length)
2192 int error, outCount;
2193 outCount = OutputToProcess(NoProc, data, length, &error);
2194 if (outCount < length) {
2195 DisplayFatalError(_("Error writing to display"), error, 1);
2200 PackHolding (char packed[], char *holding)
2210 switch (runlength) {
2221 sprintf(q, "%d", runlength);
2233 /* Telnet protocol requests from the front end */
2235 TelnetRequest (unsigned char ddww, unsigned char option)
2237 unsigned char msg[3];
2238 int outCount, outError;
2240 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2242 if (appData.debugMode) {
2243 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2259 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2268 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2271 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2276 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2278 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2285 if (!appData.icsActive) return;
2286 TelnetRequest(TN_DO, TN_ECHO);
2292 if (!appData.icsActive) return;
2293 TelnetRequest(TN_DONT, TN_ECHO);
2297 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2299 /* put the holdings sent to us by the server on the board holdings area */
2300 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2304 if(gameInfo.holdingsWidth < 2) return;
2305 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2306 return; // prevent overwriting by pre-board holdings
2308 if( (int)lowestPiece >= BlackPawn ) {
2311 holdingsStartRow = BOARD_HEIGHT-1;
2314 holdingsColumn = BOARD_WIDTH-1;
2315 countsColumn = BOARD_WIDTH-2;
2316 holdingsStartRow = 0;
2320 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2321 board[i][holdingsColumn] = EmptySquare;
2322 board[i][countsColumn] = (ChessSquare) 0;
2324 while( (p=*holdings++) != NULLCHAR ) {
2325 piece = CharToPiece( ToUpper(p) );
2326 if(piece == EmptySquare) continue;
2327 /*j = (int) piece - (int) WhitePawn;*/
2328 j = PieceToNumber(piece);
2329 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2330 if(j < 0) continue; /* should not happen */
2331 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2332 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2333 board[holdingsStartRow+j*direction][countsColumn]++;
2339 VariantSwitch (Board board, VariantClass newVariant)
2341 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2342 static Board oldBoard;
2344 startedFromPositionFile = FALSE;
2345 if(gameInfo.variant == newVariant) return;
2347 /* [HGM] This routine is called each time an assignment is made to
2348 * gameInfo.variant during a game, to make sure the board sizes
2349 * are set to match the new variant. If that means adding or deleting
2350 * holdings, we shift the playing board accordingly
2351 * This kludge is needed because in ICS observe mode, we get boards
2352 * of an ongoing game without knowing the variant, and learn about the
2353 * latter only later. This can be because of the move list we requested,
2354 * in which case the game history is refilled from the beginning anyway,
2355 * but also when receiving holdings of a crazyhouse game. In the latter
2356 * case we want to add those holdings to the already received position.
2360 if (appData.debugMode) {
2361 fprintf(debugFP, "Switch board from %s to %s\n",
2362 VariantName(gameInfo.variant), VariantName(newVariant));
2363 setbuf(debugFP, NULL);
2365 shuffleOpenings = 0; /* [HGM] shuffle */
2366 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2370 newWidth = 9; newHeight = 9;
2371 gameInfo.holdingsSize = 7;
2372 case VariantBughouse:
2373 case VariantCrazyhouse:
2374 newHoldingsWidth = 2; break;
2378 newHoldingsWidth = 2;
2379 gameInfo.holdingsSize = 8;
2382 case VariantCapablanca:
2383 case VariantCapaRandom:
2386 newHoldingsWidth = gameInfo.holdingsSize = 0;
2389 if(newWidth != gameInfo.boardWidth ||
2390 newHeight != gameInfo.boardHeight ||
2391 newHoldingsWidth != gameInfo.holdingsWidth ) {
2393 /* shift position to new playing area, if needed */
2394 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2395 for(i=0; i<BOARD_HEIGHT; i++)
2396 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2397 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2399 for(i=0; i<newHeight; i++) {
2400 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2401 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2403 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2404 for(i=0; i<BOARD_HEIGHT; i++)
2405 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2406 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2409 gameInfo.boardWidth = newWidth;
2410 gameInfo.boardHeight = newHeight;
2411 gameInfo.holdingsWidth = newHoldingsWidth;
2412 gameInfo.variant = newVariant;
2413 InitDrawingSizes(-2, 0);
2414 } else gameInfo.variant = newVariant;
2415 CopyBoard(oldBoard, board); // remember correctly formatted board
2416 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2417 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2420 static int loggedOn = FALSE;
2422 /*-- Game start info cache: --*/
2424 char gs_kind[MSG_SIZ];
2425 static char player1Name[128] = "";
2426 static char player2Name[128] = "";
2427 static char cont_seq[] = "\n\\ ";
2428 static int player1Rating = -1;
2429 static int player2Rating = -1;
2430 /*----------------------------*/
2432 ColorClass curColor = ColorNormal;
2433 int suppressKibitz = 0;
2436 Boolean soughtPending = FALSE;
2437 Boolean seekGraphUp;
2438 #define MAX_SEEK_ADS 200
2440 char *seekAdList[MAX_SEEK_ADS];
2441 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2442 float tcList[MAX_SEEK_ADS];
2443 char colorList[MAX_SEEK_ADS];
2444 int nrOfSeekAds = 0;
2445 int minRating = 1010, maxRating = 2800;
2446 int hMargin = 10, vMargin = 20, h, w;
2447 extern int squareSize, lineGap;
2452 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2453 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2454 if(r < minRating+100 && r >=0 ) r = minRating+100;
2455 if(r > maxRating) r = maxRating;
2456 if(tc < 1.) tc = 1.;
2457 if(tc > 95.) tc = 95.;
2458 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2459 y = ((double)r - minRating)/(maxRating - minRating)
2460 * (h-vMargin-squareSize/8-1) + vMargin;
2461 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2462 if(strstr(seekAdList[i], " u ")) color = 1;
2463 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2464 !strstr(seekAdList[i], "bullet") &&
2465 !strstr(seekAdList[i], "blitz") &&
2466 !strstr(seekAdList[i], "standard") ) color = 2;
2467 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2468 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2472 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2474 char buf[MSG_SIZ], *ext = "";
2475 VariantClass v = StringToVariant(type);
2476 if(strstr(type, "wild")) {
2477 ext = type + 4; // append wild number
2478 if(v == VariantFischeRandom) type = "chess960"; else
2479 if(v == VariantLoadable) type = "setup"; else
2480 type = VariantName(v);
2482 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2483 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2484 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2485 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2486 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2487 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2488 seekNrList[nrOfSeekAds] = nr;
2489 zList[nrOfSeekAds] = 0;
2490 seekAdList[nrOfSeekAds++] = StrSave(buf);
2491 if(plot) PlotSeekAd(nrOfSeekAds-1);
2496 EraseSeekDot (int i)
2498 int x = xList[i], y = yList[i], d=squareSize/4, k;
2499 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2500 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2501 // now replot every dot that overlapped
2502 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2503 int xx = xList[k], yy = yList[k];
2504 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2505 DrawSeekDot(xx, yy, colorList[k]);
2510 RemoveSeekAd (int nr)
2513 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2515 if(seekAdList[i]) free(seekAdList[i]);
2516 seekAdList[i] = seekAdList[--nrOfSeekAds];
2517 seekNrList[i] = seekNrList[nrOfSeekAds];
2518 ratingList[i] = ratingList[nrOfSeekAds];
2519 colorList[i] = colorList[nrOfSeekAds];
2520 tcList[i] = tcList[nrOfSeekAds];
2521 xList[i] = xList[nrOfSeekAds];
2522 yList[i] = yList[nrOfSeekAds];
2523 zList[i] = zList[nrOfSeekAds];
2524 seekAdList[nrOfSeekAds] = NULL;
2530 MatchSoughtLine (char *line)
2532 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2533 int nr, base, inc, u=0; char dummy;
2535 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2536 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2538 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2539 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2540 // match: compact and save the line
2541 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2551 if(!seekGraphUp) return FALSE;
2552 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2553 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2555 DrawSeekBackground(0, 0, w, h);
2556 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2557 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2558 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2559 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2561 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2564 snprintf(buf, MSG_SIZ, "%d", i);
2565 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2568 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2569 for(i=1; i<100; i+=(i<10?1:5)) {
2570 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2571 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2572 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2574 snprintf(buf, MSG_SIZ, "%d", i);
2575 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2578 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2583 SeekGraphClick (ClickType click, int x, int y, int moving)
2585 static int lastDown = 0, displayed = 0, lastSecond;
2586 if(y < 0) return FALSE;
2587 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2588 if(click == Release || moving) return FALSE;
2590 soughtPending = TRUE;
2591 SendToICS(ics_prefix);
2592 SendToICS("sought\n"); // should this be "sought all"?
2593 } else { // issue challenge based on clicked ad
2594 int dist = 10000; int i, closest = 0, second = 0;
2595 for(i=0; i<nrOfSeekAds; i++) {
2596 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2597 if(d < dist) { dist = d; closest = i; }
2598 second += (d - zList[i] < 120); // count in-range ads
2599 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2603 second = (second > 1);
2604 if(displayed != closest || second != lastSecond) {
2605 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2606 lastSecond = second; displayed = closest;
2608 if(click == Press) {
2609 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2612 } // on press 'hit', only show info
2613 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2614 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2615 SendToICS(ics_prefix);
2617 return TRUE; // let incoming board of started game pop down the graph
2618 } else if(click == Release) { // release 'miss' is ignored
2619 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2620 if(moving == 2) { // right up-click
2621 nrOfSeekAds = 0; // refresh graph
2622 soughtPending = TRUE;
2623 SendToICS(ics_prefix);
2624 SendToICS("sought\n"); // should this be "sought all"?
2627 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2628 // press miss or release hit 'pop down' seek graph
2629 seekGraphUp = FALSE;
2630 DrawPosition(TRUE, NULL);
2636 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2638 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2639 #define STARTED_NONE 0
2640 #define STARTED_MOVES 1
2641 #define STARTED_BOARD 2
2642 #define STARTED_OBSERVE 3
2643 #define STARTED_HOLDINGS 4
2644 #define STARTED_CHATTER 5
2645 #define STARTED_COMMENT 6
2646 #define STARTED_MOVES_NOHIDE 7
2648 static int started = STARTED_NONE;
2649 static char parse[20000];
2650 static int parse_pos = 0;
2651 static char buf[BUF_SIZE + 1];
2652 static int firstTime = TRUE, intfSet = FALSE;
2653 static ColorClass prevColor = ColorNormal;
2654 static int savingComment = FALSE;
2655 static int cmatch = 0; // continuation sequence match
2662 int backup; /* [DM] For zippy color lines */
2664 char talker[MSG_SIZ]; // [HGM] chat
2667 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2669 if (appData.debugMode) {
2671 fprintf(debugFP, "<ICS: ");
2672 show_bytes(debugFP, data, count);
2673 fprintf(debugFP, "\n");
2677 if (appData.debugMode) { int f = forwardMostMove;
2678 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2679 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2680 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2683 /* If last read ended with a partial line that we couldn't parse,
2684 prepend it to the new read and try again. */
2685 if (leftover_len > 0) {
2686 for (i=0; i<leftover_len; i++)
2687 buf[i] = buf[leftover_start + i];
2690 /* copy new characters into the buffer */
2691 bp = buf + leftover_len;
2692 buf_len=leftover_len;
2693 for (i=0; i<count; i++)
2696 if (data[i] == '\r')
2699 // join lines split by ICS?
2700 if (!appData.noJoin)
2703 Joining just consists of finding matches against the
2704 continuation sequence, and discarding that sequence
2705 if found instead of copying it. So, until a match
2706 fails, there's nothing to do since it might be the
2707 complete sequence, and thus, something we don't want
2710 if (data[i] == cont_seq[cmatch])
2713 if (cmatch == strlen(cont_seq))
2715 cmatch = 0; // complete match. just reset the counter
2718 it's possible for the ICS to not include the space
2719 at the end of the last word, making our [correct]
2720 join operation fuse two separate words. the server
2721 does this when the space occurs at the width setting.
2723 if (!buf_len || buf[buf_len-1] != ' ')
2734 match failed, so we have to copy what matched before
2735 falling through and copying this character. In reality,
2736 this will only ever be just the newline character, but
2737 it doesn't hurt to be precise.
2739 strncpy(bp, cont_seq, cmatch);
2751 buf[buf_len] = NULLCHAR;
2752 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2757 while (i < buf_len) {
2758 /* Deal with part of the TELNET option negotiation
2759 protocol. We refuse to do anything beyond the
2760 defaults, except that we allow the WILL ECHO option,
2761 which ICS uses to turn off password echoing when we are
2762 directly connected to it. We reject this option
2763 if localLineEditing mode is on (always on in xboard)
2764 and we are talking to port 23, which might be a real
2765 telnet server that will try to keep WILL ECHO on permanently.
2767 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2768 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2769 unsigned char option;
2771 switch ((unsigned char) buf[++i]) {
2773 if (appData.debugMode)
2774 fprintf(debugFP, "\n<WILL ");
2775 switch (option = (unsigned char) buf[++i]) {
2777 if (appData.debugMode)
2778 fprintf(debugFP, "ECHO ");
2779 /* Reply only if this is a change, according
2780 to the protocol rules. */
2781 if (remoteEchoOption) break;
2782 if (appData.localLineEditing &&
2783 atoi(appData.icsPort) == TN_PORT) {
2784 TelnetRequest(TN_DONT, TN_ECHO);
2787 TelnetRequest(TN_DO, TN_ECHO);
2788 remoteEchoOption = TRUE;
2792 if (appData.debugMode)
2793 fprintf(debugFP, "%d ", option);
2794 /* Whatever this is, we don't want it. */
2795 TelnetRequest(TN_DONT, option);
2800 if (appData.debugMode)
2801 fprintf(debugFP, "\n<WONT ");
2802 switch (option = (unsigned char) buf[++i]) {
2804 if (appData.debugMode)
2805 fprintf(debugFP, "ECHO ");
2806 /* Reply only if this is a change, according
2807 to the protocol rules. */
2808 if (!remoteEchoOption) break;
2810 TelnetRequest(TN_DONT, TN_ECHO);
2811 remoteEchoOption = FALSE;
2814 if (appData.debugMode)
2815 fprintf(debugFP, "%d ", (unsigned char) option);
2816 /* Whatever this is, it must already be turned
2817 off, because we never agree to turn on
2818 anything non-default, so according to the
2819 protocol rules, we don't reply. */
2824 if (appData.debugMode)
2825 fprintf(debugFP, "\n<DO ");
2826 switch (option = (unsigned char) buf[++i]) {
2828 /* Whatever this is, we refuse to do it. */
2829 if (appData.debugMode)
2830 fprintf(debugFP, "%d ", option);
2831 TelnetRequest(TN_WONT, option);
2836 if (appData.debugMode)
2837 fprintf(debugFP, "\n<DONT ");
2838 switch (option = (unsigned char) buf[++i]) {
2840 if (appData.debugMode)
2841 fprintf(debugFP, "%d ", option);
2842 /* Whatever this is, we are already not doing
2843 it, because we never agree to do anything
2844 non-default, so according to the protocol
2845 rules, we don't reply. */
2850 if (appData.debugMode)
2851 fprintf(debugFP, "\n<IAC ");
2852 /* Doubled IAC; pass it through */
2856 if (appData.debugMode)
2857 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2858 /* Drop all other telnet commands on the floor */
2861 if (oldi > next_out)
2862 SendToPlayer(&buf[next_out], oldi - next_out);
2868 /* OK, this at least will *usually* work */
2869 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2873 if (loggedOn && !intfSet) {
2874 if (ics_type == ICS_ICC) {
2875 snprintf(str, MSG_SIZ,
2876 "/set-quietly interface %s\n/set-quietly style 12\n",
2878 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2879 strcat(str, "/set-2 51 1\n/set seek 1\n");
2880 } else if (ics_type == ICS_CHESSNET) {
2881 snprintf(str, MSG_SIZ, "/style 12\n");
2883 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2884 strcat(str, programVersion);
2885 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2886 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2889 strcat(str, "$iset nohighlight 1\n");
2891 strcat(str, "$iset lock 1\n$style 12\n");
2894 NotifyFrontendLogin();
2898 if (started == STARTED_COMMENT) {
2899 /* Accumulate characters in comment */
2900 parse[parse_pos++] = buf[i];
2901 if (buf[i] == '\n') {
2902 parse[parse_pos] = NULLCHAR;
2903 if(chattingPartner>=0) {
2905 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2906 OutputChatMessage(chattingPartner, mess);
2907 chattingPartner = -1;
2908 next_out = i+1; // [HGM] suppress printing in ICS window
2910 if(!suppressKibitz) // [HGM] kibitz
2911 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2912 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2913 int nrDigit = 0, nrAlph = 0, j;
2914 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2915 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2916 parse[parse_pos] = NULLCHAR;
2917 // try to be smart: if it does not look like search info, it should go to
2918 // ICS interaction window after all, not to engine-output window.
2919 for(j=0; j<parse_pos; j++) { // count letters and digits
2920 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2921 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2922 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2924 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2925 int depth=0; float score;
2926 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2927 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2928 pvInfoList[forwardMostMove-1].depth = depth;
2929 pvInfoList[forwardMostMove-1].score = 100*score;
2931 OutputKibitz(suppressKibitz, parse);
2934 if(gameMode == IcsObserving) // restore original ICS messages
2935 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2937 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2938 SendToPlayer(tmp, strlen(tmp));
2940 next_out = i+1; // [HGM] suppress printing in ICS window
2942 started = STARTED_NONE;
2944 /* Don't match patterns against characters in comment */
2949 if (started == STARTED_CHATTER) {
2950 if (buf[i] != '\n') {
2951 /* Don't match patterns against characters in chatter */
2955 started = STARTED_NONE;
2956 if(suppressKibitz) next_out = i+1;
2959 /* Kludge to deal with rcmd protocol */
2960 if (firstTime && looking_at(buf, &i, "\001*")) {
2961 DisplayFatalError(&buf[1], 0, 1);
2967 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2970 if (appData.debugMode)
2971 fprintf(debugFP, "ics_type %d\n", ics_type);
2974 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2975 ics_type = ICS_FICS;
2977 if (appData.debugMode)
2978 fprintf(debugFP, "ics_type %d\n", ics_type);
2981 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2982 ics_type = ICS_CHESSNET;
2984 if (appData.debugMode)
2985 fprintf(debugFP, "ics_type %d\n", ics_type);
2990 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2991 looking_at(buf, &i, "Logging you in as \"*\"") ||
2992 looking_at(buf, &i, "will be \"*\""))) {
2993 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2997 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2999 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3000 DisplayIcsInteractionTitle(buf);
3001 have_set_title = TRUE;
3004 /* skip finger notes */
3005 if (started == STARTED_NONE &&
3006 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3007 (buf[i] == '1' && buf[i+1] == '0')) &&
3008 buf[i+2] == ':' && buf[i+3] == ' ') {
3009 started = STARTED_CHATTER;
3015 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3016 if(appData.seekGraph) {
3017 if(soughtPending && MatchSoughtLine(buf+i)) {
3018 i = strstr(buf+i, "rated") - buf;
3019 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3020 next_out = leftover_start = i;
3021 started = STARTED_CHATTER;
3022 suppressKibitz = TRUE;
3025 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3026 && looking_at(buf, &i, "* ads displayed")) {
3027 soughtPending = FALSE;
3032 if(appData.autoRefresh) {
3033 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3034 int s = (ics_type == ICS_ICC); // ICC format differs
3036 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3037 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3038 looking_at(buf, &i, "*% "); // eat prompt
3039 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3040 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041 next_out = i; // suppress
3044 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3045 char *p = star_match[0];
3047 if(seekGraphUp) RemoveSeekAd(atoi(p));
3048 while(*p && *p++ != ' '); // next
3050 looking_at(buf, &i, "*% "); // eat prompt
3051 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3058 /* skip formula vars */
3059 if (started == STARTED_NONE &&
3060 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3061 started = STARTED_CHATTER;
3066 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3067 if (appData.autoKibitz && started == STARTED_NONE &&
3068 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3069 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3070 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3071 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3072 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3073 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3074 suppressKibitz = TRUE;
3075 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3077 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3078 && (gameMode == IcsPlayingWhite)) ||
3079 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3080 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3081 started = STARTED_CHATTER; // own kibitz we simply discard
3083 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3084 parse_pos = 0; parse[0] = NULLCHAR;
3085 savingComment = TRUE;
3086 suppressKibitz = gameMode != IcsObserving ? 2 :
3087 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3091 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3092 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3093 && atoi(star_match[0])) {
3094 // suppress the acknowledgements of our own autoKibitz
3096 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3098 SendToPlayer(star_match[0], strlen(star_match[0]));
3099 if(looking_at(buf, &i, "*% ")) // eat prompt
3100 suppressKibitz = FALSE;
3104 } // [HGM] kibitz: end of patch
3106 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3108 // [HGM] chat: intercept tells by users for which we have an open chat window
3110 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3111 looking_at(buf, &i, "* whispers:") ||
3112 looking_at(buf, &i, "* kibitzes:") ||
3113 looking_at(buf, &i, "* shouts:") ||
3114 looking_at(buf, &i, "* c-shouts:") ||
3115 looking_at(buf, &i, "--> * ") ||
3116 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3117 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3118 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3119 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3121 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3122 chattingPartner = -1;
3124 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3125 for(p=0; p<MAX_CHAT; p++) {
3126 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3127 talker[0] = '['; strcat(talker, "] ");
3128 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3129 chattingPartner = p; break;
3132 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3133 for(p=0; p<MAX_CHAT; p++) {
3134 if(!strcmp("kibitzes", chatPartner[p])) {
3135 talker[0] = '['; strcat(talker, "] ");
3136 chattingPartner = p; break;
3139 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3140 for(p=0; p<MAX_CHAT; p++) {
3141 if(!strcmp("whispers", chatPartner[p])) {
3142 talker[0] = '['; strcat(talker, "] ");
3143 chattingPartner = p; break;
3146 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3147 if(buf[i-8] == '-' && buf[i-3] == 't')
3148 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3149 if(!strcmp("c-shouts", chatPartner[p])) {
3150 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3151 chattingPartner = p; break;
3154 if(chattingPartner < 0)
3155 for(p=0; p<MAX_CHAT; p++) {
3156 if(!strcmp("shouts", chatPartner[p])) {
3157 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3158 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3159 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3160 chattingPartner = p; break;
3164 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3165 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3166 talker[0] = 0; Colorize(ColorTell, FALSE);
3167 chattingPartner = p; break;
3169 if(chattingPartner<0) i = oldi; else {
3170 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3171 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3172 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173 started = STARTED_COMMENT;
3174 parse_pos = 0; parse[0] = NULLCHAR;
3175 savingComment = 3 + chattingPartner; // counts as TRUE
3176 suppressKibitz = TRUE;
3179 } // [HGM] chat: end of patch
3182 if (appData.zippyTalk || appData.zippyPlay) {
3183 /* [DM] Backup address for color zippy lines */
3185 if (loggedOn == TRUE)
3186 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3187 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3189 } // [DM] 'else { ' deleted
3191 /* Regular tells and says */
3192 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3193 looking_at(buf, &i, "* (your partner) tells you: ") ||
3194 looking_at(buf, &i, "* says: ") ||
3195 /* Don't color "message" or "messages" output */
3196 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3197 looking_at(buf, &i, "*. * at *:*: ") ||
3198 looking_at(buf, &i, "--* (*:*): ") ||
3199 /* Message notifications (same color as tells) */
3200 looking_at(buf, &i, "* has left a message ") ||
3201 looking_at(buf, &i, "* just sent you a message:\n") ||
3202 /* Whispers and kibitzes */
3203 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3204 looking_at(buf, &i, "* kibitzes: ") ||
3206 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3208 if (tkind == 1 && strchr(star_match[0], ':')) {
3209 /* Avoid "tells you:" spoofs in channels */
3212 if (star_match[0][0] == NULLCHAR ||
3213 strchr(star_match[0], ' ') ||
3214 (tkind == 3 && strchr(star_match[1], ' '))) {
3215 /* Reject bogus matches */
3218 if (appData.colorize) {
3219 if (oldi > next_out) {
3220 SendToPlayer(&buf[next_out], oldi - next_out);
3225 Colorize(ColorTell, FALSE);
3226 curColor = ColorTell;
3229 Colorize(ColorKibitz, FALSE);
3230 curColor = ColorKibitz;
3233 p = strrchr(star_match[1], '(');
3240 Colorize(ColorChannel1, FALSE);
3241 curColor = ColorChannel1;
3243 Colorize(ColorChannel, FALSE);
3244 curColor = ColorChannel;
3248 curColor = ColorNormal;
3252 if (started == STARTED_NONE && appData.autoComment &&
3253 (gameMode == IcsObserving ||
3254 gameMode == IcsPlayingWhite ||
3255 gameMode == IcsPlayingBlack)) {
3256 parse_pos = i - oldi;
3257 memcpy(parse, &buf[oldi], parse_pos);
3258 parse[parse_pos] = NULLCHAR;
3259 started = STARTED_COMMENT;
3260 savingComment = TRUE;
3262 started = STARTED_CHATTER;
3263 savingComment = FALSE;
3270 if (looking_at(buf, &i, "* s-shouts: ") ||
3271 looking_at(buf, &i, "* c-shouts: ")) {
3272 if (appData.colorize) {
3273 if (oldi > next_out) {
3274 SendToPlayer(&buf[next_out], oldi - next_out);
3277 Colorize(ColorSShout, FALSE);
3278 curColor = ColorSShout;
3281 started = STARTED_CHATTER;
3285 if (looking_at(buf, &i, "--->")) {
3290 if (looking_at(buf, &i, "* shouts: ") ||
3291 looking_at(buf, &i, "--> ")) {
3292 if (appData.colorize) {
3293 if (oldi > next_out) {
3294 SendToPlayer(&buf[next_out], oldi - next_out);
3297 Colorize(ColorShout, FALSE);
3298 curColor = ColorShout;
3301 started = STARTED_CHATTER;
3305 if (looking_at( buf, &i, "Challenge:")) {
3306 if (appData.colorize) {
3307 if (oldi > next_out) {
3308 SendToPlayer(&buf[next_out], oldi - next_out);
3311 Colorize(ColorChallenge, FALSE);
3312 curColor = ColorChallenge;
3318 if (looking_at(buf, &i, "* offers you") ||
3319 looking_at(buf, &i, "* offers to be") ||
3320 looking_at(buf, &i, "* would like to") ||
3321 looking_at(buf, &i, "* requests to") ||
3322 looking_at(buf, &i, "Your opponent offers") ||
3323 looking_at(buf, &i, "Your opponent requests")) {
3325 if (appData.colorize) {
3326 if (oldi > next_out) {
3327 SendToPlayer(&buf[next_out], oldi - next_out);
3330 Colorize(ColorRequest, FALSE);
3331 curColor = ColorRequest;
3336 if (looking_at(buf, &i, "* (*) seeking")) {
3337 if (appData.colorize) {
3338 if (oldi > next_out) {
3339 SendToPlayer(&buf[next_out], oldi - next_out);
3342 Colorize(ColorSeek, FALSE);
3343 curColor = ColorSeek;
3348 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3350 if (looking_at(buf, &i, "\\ ")) {
3351 if (prevColor != ColorNormal) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3356 Colorize(prevColor, TRUE);
3357 curColor = prevColor;
3359 if (savingComment) {
3360 parse_pos = i - oldi;
3361 memcpy(parse, &buf[oldi], parse_pos);
3362 parse[parse_pos] = NULLCHAR;
3363 started = STARTED_COMMENT;
3364 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3365 chattingPartner = savingComment - 3; // kludge to remember the box
3367 started = STARTED_CHATTER;
3372 if (looking_at(buf, &i, "Black Strength :") ||
3373 looking_at(buf, &i, "<<< style 10 board >>>") ||
3374 looking_at(buf, &i, "<10>") ||
3375 looking_at(buf, &i, "#@#")) {
3376 /* Wrong board style */
3378 SendToICS(ics_prefix);
3379 SendToICS("set style 12\n");
3380 SendToICS(ics_prefix);
3381 SendToICS("refresh\n");
3385 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3387 have_sent_ICS_logon = 1;
3391 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3392 (looking_at(buf, &i, "\n<12> ") ||
3393 looking_at(buf, &i, "<12> "))) {
3395 if (oldi > next_out) {
3396 SendToPlayer(&buf[next_out], oldi - next_out);
3399 started = STARTED_BOARD;
3404 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3405 looking_at(buf, &i, "<b1> ")) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 started = STARTED_HOLDINGS;
3415 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3417 /* Header for a move list -- first line */
3419 switch (ics_getting_history) {
3423 case BeginningOfGame:
3424 /* User typed "moves" or "oldmoves" while we
3425 were idle. Pretend we asked for these
3426 moves and soak them up so user can step
3427 through them and/or save them.
3430 gameMode = IcsObserving;
3433 ics_getting_history = H_GOT_UNREQ_HEADER;
3435 case EditGame: /*?*/
3436 case EditPosition: /*?*/
3437 /* Should above feature work in these modes too? */
3438 /* For now it doesn't */
3439 ics_getting_history = H_GOT_UNWANTED_HEADER;
3442 ics_getting_history = H_GOT_UNWANTED_HEADER;
3447 /* Is this the right one? */
3448 if (gameInfo.white && gameInfo.black &&
3449 strcmp(gameInfo.white, star_match[0]) == 0 &&
3450 strcmp(gameInfo.black, star_match[2]) == 0) {
3452 ics_getting_history = H_GOT_REQ_HEADER;
3455 case H_GOT_REQ_HEADER:
3456 case H_GOT_UNREQ_HEADER:
3457 case H_GOT_UNWANTED_HEADER:
3458 case H_GETTING_MOVES:
3459 /* Should not happen */
3460 DisplayError(_("Error gathering move list: two headers"), 0);
3461 ics_getting_history = H_FALSE;
3465 /* Save player ratings into gameInfo if needed */
3466 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3467 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3468 (gameInfo.whiteRating == -1 ||
3469 gameInfo.blackRating == -1)) {
3471 gameInfo.whiteRating = string_to_rating(star_match[1]);
3472 gameInfo.blackRating = string_to_rating(star_match[3]);
3473 if (appData.debugMode)
3474 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3475 gameInfo.whiteRating, gameInfo.blackRating);
3480 if (looking_at(buf, &i,
3481 "* * match, initial time: * minute*, increment: * second")) {
3482 /* Header for a move list -- second line */
3483 /* Initial board will follow if this is a wild game */
3484 if (gameInfo.event != NULL) free(gameInfo.event);
3485 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3486 gameInfo.event = StrSave(str);
3487 /* [HGM] we switched variant. Translate boards if needed. */
3488 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3492 if (looking_at(buf, &i, "Move ")) {
3493 /* Beginning of a move list */
3494 switch (ics_getting_history) {
3496 /* Normally should not happen */
3497 /* Maybe user hit reset while we were parsing */
3500 /* Happens if we are ignoring a move list that is not
3501 * the one we just requested. Common if the user
3502 * tries to observe two games without turning off
3505 case H_GETTING_MOVES:
3506 /* Should not happen */
3507 DisplayError(_("Error gathering move list: nested"), 0);
3508 ics_getting_history = H_FALSE;
3510 case H_GOT_REQ_HEADER:
3511 ics_getting_history = H_GETTING_MOVES;
3512 started = STARTED_MOVES;
3514 if (oldi > next_out) {
3515 SendToPlayer(&buf[next_out], oldi - next_out);
3518 case H_GOT_UNREQ_HEADER:
3519 ics_getting_history = H_GETTING_MOVES;
3520 started = STARTED_MOVES_NOHIDE;
3523 case H_GOT_UNWANTED_HEADER:
3524 ics_getting_history = H_FALSE;
3530 if (looking_at(buf, &i, "% ") ||
3531 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3532 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3533 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3534 soughtPending = FALSE;
3538 if(suppressKibitz) next_out = i;
3539 savingComment = FALSE;
3543 case STARTED_MOVES_NOHIDE:
3544 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3545 parse[parse_pos + i - oldi] = NULLCHAR;
3546 ParseGameHistory(parse);
3548 if (appData.zippyPlay && first.initDone) {
3549 FeedMovesToProgram(&first, forwardMostMove);
3550 if (gameMode == IcsPlayingWhite) {
3551 if (WhiteOnMove(forwardMostMove)) {
3552 if (first.sendTime) {
3553 if (first.useColors) {
3554 SendToProgram("black\n", &first);
3556 SendTimeRemaining(&first, TRUE);
3558 if (first.useColors) {
3559 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3561 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3562 first.maybeThinking = TRUE;
3564 if (first.usePlayother) {
3565 if (first.sendTime) {
3566 SendTimeRemaining(&first, TRUE);
3568 SendToProgram("playother\n", &first);
3574 } else if (gameMode == IcsPlayingBlack) {
3575 if (!WhiteOnMove(forwardMostMove)) {
3576 if (first.sendTime) {
3577 if (first.useColors) {
3578 SendToProgram("white\n", &first);
3580 SendTimeRemaining(&first, FALSE);
3582 if (first.useColors) {
3583 SendToProgram("black\n", &first);
3585 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3586 first.maybeThinking = TRUE;
3588 if (first.usePlayother) {
3589 if (first.sendTime) {
3590 SendTimeRemaining(&first, FALSE);
3592 SendToProgram("playother\n", &first);
3601 if (gameMode == IcsObserving && ics_gamenum == -1) {
3602 /* Moves came from oldmoves or moves command
3603 while we weren't doing anything else.
3605 currentMove = forwardMostMove;
3606 ClearHighlights();/*!!could figure this out*/
3607 flipView = appData.flipView;
3608 DrawPosition(TRUE, boards[currentMove]);
3609 DisplayBothClocks();
3610 snprintf(str, MSG_SIZ, "%s %s %s",
3611 gameInfo.white, _("vs."), gameInfo.black);
3615 /* Moves were history of an active game */
3616 if (gameInfo.resultDetails != NULL) {
3617 free(gameInfo.resultDetails);
3618 gameInfo.resultDetails = NULL;
3621 HistorySet(parseList, backwardMostMove,
3622 forwardMostMove, currentMove-1);
3623 DisplayMove(currentMove - 1);
3624 if (started == STARTED_MOVES) next_out = i;
3625 started = STARTED_NONE;
3626 ics_getting_history = H_FALSE;
3629 case STARTED_OBSERVE:
3630 started = STARTED_NONE;
3631 SendToICS(ics_prefix);
3632 SendToICS("refresh\n");
3638 if(bookHit) { // [HGM] book: simulate book reply
3639 static char bookMove[MSG_SIZ]; // a bit generous?
3641 programStats.nodes = programStats.depth = programStats.time =
3642 programStats.score = programStats.got_only_move = 0;
3643 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3645 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3646 strcat(bookMove, bookHit);
3647 HandleMachineMove(bookMove, &first);
3652 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3653 started == STARTED_HOLDINGS ||
3654 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3655 /* Accumulate characters in move list or board */
3656 parse[parse_pos++] = buf[i];
3659 /* Start of game messages. Mostly we detect start of game
3660 when the first board image arrives. On some versions
3661 of the ICS, though, we need to do a "refresh" after starting
3662 to observe in order to get the current board right away. */
3663 if (looking_at(buf, &i, "Adding game * to observation list")) {
3664 started = STARTED_OBSERVE;
3668 /* Handle auto-observe */
3669 if (appData.autoObserve &&
3670 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3671 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3673 /* Choose the player that was highlighted, if any. */
3674 if (star_match[0][0] == '\033' ||
3675 star_match[1][0] != '\033') {
3676 player = star_match[0];
3678 player = star_match[2];
3680 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3681 ics_prefix, StripHighlightAndTitle(player));
3684 /* Save ratings from notify string */
3685 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3686 player1Rating = string_to_rating(star_match[1]);
3687 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3688 player2Rating = string_to_rating(star_match[3]);
3690 if (appData.debugMode)
3692 "Ratings from 'Game notification:' %s %d, %s %d\n",
3693 player1Name, player1Rating,
3694 player2Name, player2Rating);
3699 /* Deal with automatic examine mode after a game,
3700 and with IcsObserving -> IcsExamining transition */
3701 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3702 looking_at(buf, &i, "has made you an examiner of game *")) {
3704 int gamenum = atoi(star_match[0]);
3705 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3706 gamenum == ics_gamenum) {
3707 /* We were already playing or observing this game;
3708 no need to refetch history */
3709 gameMode = IcsExamining;
3711 pauseExamForwardMostMove = forwardMostMove;
3712 } else if (currentMove < forwardMostMove) {
3713 ForwardInner(forwardMostMove);
3716 /* I don't think this case really can happen */
3717 SendToICS(ics_prefix);
3718 SendToICS("refresh\n");
3723 /* Error messages */
3724 // if (ics_user_moved) {
3725 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3726 if (looking_at(buf, &i, "Illegal move") ||
3727 looking_at(buf, &i, "Not a legal move") ||
3728 looking_at(buf, &i, "Your king is in check") ||
3729 looking_at(buf, &i, "It isn't your turn") ||
3730 looking_at(buf, &i, "It is not your move")) {
3732 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3733 currentMove = forwardMostMove-1;
3734 DisplayMove(currentMove - 1); /* before DMError */
3735 DrawPosition(FALSE, boards[currentMove]);
3736 SwitchClocks(forwardMostMove-1); // [HGM] race
3737 DisplayBothClocks();
3739 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3745 if (looking_at(buf, &i, "still have time") ||
3746 looking_at(buf, &i, "not out of time") ||
3747 looking_at(buf, &i, "either player is out of time") ||
3748 looking_at(buf, &i, "has timeseal; checking")) {
3749 /* We must have called his flag a little too soon */
3750 whiteFlag = blackFlag = FALSE;
3754 if (looking_at(buf, &i, "added * seconds to") ||
3755 looking_at(buf, &i, "seconds were added to")) {
3756 /* Update the clocks */
3757 SendToICS(ics_prefix);
3758 SendToICS("refresh\n");
3762 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3763 ics_clock_paused = TRUE;
3768 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3769 ics_clock_paused = FALSE;
3774 /* Grab player ratings from the Creating: message.
3775 Note we have to check for the special case when
3776 the ICS inserts things like [white] or [black]. */
3777 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3778 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3780 0 player 1 name (not necessarily white)
3782 2 empty, white, or black (IGNORED)
3783 3 player 2 name (not necessarily black)
3786 The names/ratings are sorted out when the game
3787 actually starts (below).
3789 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3790 player1Rating = string_to_rating(star_match[1]);
3791 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3792 player2Rating = string_to_rating(star_match[4]);
3794 if (appData.debugMode)
3796 "Ratings from 'Creating:' %s %d, %s %d\n",
3797 player1Name, player1Rating,
3798 player2Name, player2Rating);
3803 /* Improved generic start/end-of-game messages */
3804 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3805 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3806 /* If tkind == 0: */
3807 /* star_match[0] is the game number */
3808 /* [1] is the white player's name */
3809 /* [2] is the black player's name */
3810 /* For end-of-game: */
3811 /* [3] is the reason for the game end */
3812 /* [4] is a PGN end game-token, preceded by " " */
3813 /* For start-of-game: */
3814 /* [3] begins with "Creating" or "Continuing" */
3815 /* [4] is " *" or empty (don't care). */
3816 int gamenum = atoi(star_match[0]);
3817 char *whitename, *blackname, *why, *endtoken;
3818 ChessMove endtype = EndOfFile;
3821 whitename = star_match[1];
3822 blackname = star_match[2];
3823 why = star_match[3];
3824 endtoken = star_match[4];
3826 whitename = star_match[1];
3827 blackname = star_match[3];
3828 why = star_match[5];
3829 endtoken = star_match[6];
3832 /* Game start messages */
3833 if (strncmp(why, "Creating ", 9) == 0 ||
3834 strncmp(why, "Continuing ", 11) == 0) {
3835 gs_gamenum = gamenum;
3836 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3837 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3838 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3840 if (appData.zippyPlay) {
3841 ZippyGameStart(whitename, blackname);
3844 partnerBoardValid = FALSE; // [HGM] bughouse
3848 /* Game end messages */
3849 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3850 ics_gamenum != gamenum) {
3853 while (endtoken[0] == ' ') endtoken++;
3854 switch (endtoken[0]) {
3857 endtype = GameUnfinished;
3860 endtype = BlackWins;
3863 if (endtoken[1] == '/')
3864 endtype = GameIsDrawn;
3866 endtype = WhiteWins;
3869 GameEnds(endtype, why, GE_ICS);
3871 if (appData.zippyPlay && first.initDone) {
3872 ZippyGameEnd(endtype, why);
3873 if (first.pr == NoProc) {
3874 /* Start the next process early so that we'll
3875 be ready for the next challenge */
3876 StartChessProgram(&first);
3878 /* Send "new" early, in case this command takes
3879 a long time to finish, so that we'll be ready
3880 for the next challenge. */
3881 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3885 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3889 if (looking_at(buf, &i, "Removing game * from observation") ||
3890 looking_at(buf, &i, "no longer observing game *") ||
3891 looking_at(buf, &i, "Game * (*) has no examiners")) {
3892 if (gameMode == IcsObserving &&
3893 atoi(star_match[0]) == ics_gamenum)
3895 /* icsEngineAnalyze */
3896 if (appData.icsEngineAnalyze) {
3903 ics_user_moved = FALSE;
3908 if (looking_at(buf, &i, "no longer examining game *")) {
3909 if (gameMode == IcsExamining &&
3910 atoi(star_match[0]) == ics_gamenum)
3914 ics_user_moved = FALSE;
3919 /* Advance leftover_start past any newlines we find,
3920 so only partial lines can get reparsed */
3921 if (looking_at(buf, &i, "\n")) {
3922 prevColor = curColor;
3923 if (curColor != ColorNormal) {
3924 if (oldi > next_out) {
3925 SendToPlayer(&buf[next_out], oldi - next_out);
3928 Colorize(ColorNormal, FALSE);
3929 curColor = ColorNormal;
3931 if (started == STARTED_BOARD) {
3932 started = STARTED_NONE;
3933 parse[parse_pos] = NULLCHAR;
3934 ParseBoard12(parse);
3937 /* Send premove here */
3938 if (appData.premove) {
3940 if (currentMove == 0 &&
3941 gameMode == IcsPlayingWhite &&
3942 appData.premoveWhite) {
3943 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3944 if (appData.debugMode)
3945 fprintf(debugFP, "Sending premove:\n");
3947 } else if (currentMove == 1 &&
3948 gameMode == IcsPlayingBlack &&
3949 appData.premoveBlack) {
3950 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3951 if (appData.debugMode)
3952 fprintf(debugFP, "Sending premove:\n");
3954 } else if (gotPremove) {
3956 ClearPremoveHighlights();
3957 if (appData.debugMode)
3958 fprintf(debugFP, "Sending premove:\n");
3959 UserMoveEvent(premoveFromX, premoveFromY,
3960 premoveToX, premoveToY,
3965 /* Usually suppress following prompt */
3966 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3967 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3968 if (looking_at(buf, &i, "*% ")) {
3969 savingComment = FALSE;
3974 } else if (started == STARTED_HOLDINGS) {
3976 char new_piece[MSG_SIZ];
3977 started = STARTED_NONE;
3978 parse[parse_pos] = NULLCHAR;
3979 if (appData.debugMode)
3980 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3981 parse, currentMove);
3982 if (sscanf(parse, " game %d", &gamenum) == 1) {
3983 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3984 if (gameInfo.variant == VariantNormal) {
3985 /* [HGM] We seem to switch variant during a game!
3986 * Presumably no holdings were displayed, so we have
3987 * to move the position two files to the right to
3988 * create room for them!
3990 VariantClass newVariant;
3991 switch(gameInfo.boardWidth) { // base guess on board width
3992 case 9: newVariant = VariantShogi; break;
3993 case 10: newVariant = VariantGreat; break;
3994 default: newVariant = VariantCrazyhouse; break;
3996 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3997 /* Get a move list just to see the header, which
3998 will tell us whether this is really bug or zh */
3999 if (ics_getting_history == H_FALSE) {
4000 ics_getting_history = H_REQUESTED;
4001 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4005 new_piece[0] = NULLCHAR;
4006 sscanf(parse, "game %d white [%s black [%s <- %s",
4007 &gamenum, white_holding, black_holding,
4009 white_holding[strlen(white_holding)-1] = NULLCHAR;
4010 black_holding[strlen(black_holding)-1] = NULLCHAR;
4011 /* [HGM] copy holdings to board holdings area */
4012 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4013 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4014 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4016 if (appData.zippyPlay && first.initDone) {
4017 ZippyHoldings(white_holding, black_holding,
4021 if (tinyLayout || smallLayout) {
4022 char wh[16], bh[16];
4023 PackHolding(wh, white_holding);
4024 PackHolding(bh, black_holding);
4025 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4026 gameInfo.white, gameInfo.black);
4028 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4029 gameInfo.white, white_holding, _("vs."),
4030 gameInfo.black, black_holding);
4032 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4033 DrawPosition(FALSE, boards[currentMove]);
4035 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4036 sscanf(parse, "game %d white [%s black [%s <- %s",
4037 &gamenum, white_holding, black_holding,
4039 white_holding[strlen(white_holding)-1] = NULLCHAR;
4040 black_holding[strlen(black_holding)-1] = NULLCHAR;
4041 /* [HGM] copy holdings to partner-board holdings area */
4042 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4043 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4044 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4045 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4046 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4049 /* Suppress following prompt */
4050 if (looking_at(buf, &i, "*% ")) {
4051 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4052 savingComment = FALSE;
4060 i++; /* skip unparsed character and loop back */
4063 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4064 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4065 // SendToPlayer(&buf[next_out], i - next_out);
4066 started != STARTED_HOLDINGS && leftover_start > next_out) {
4067 SendToPlayer(&buf[next_out], leftover_start - next_out);
4071 leftover_len = buf_len - leftover_start;
4072 /* if buffer ends with something we couldn't parse,
4073 reparse it after appending the next read */
4075 } else if (count == 0) {
4076 RemoveInputSource(isr);
4077 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4079 DisplayFatalError(_("Error reading from ICS"), error, 1);
4084 /* Board style 12 looks like this:
4086 <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
4088 * The "<12> " is stripped before it gets to this routine. The two
4089 * trailing 0's (flip state and clock ticking) are later addition, and
4090 * some chess servers may not have them, or may have only the first.
4091 * Additional trailing fields may be added in the future.
4094 #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"
4096 #define RELATION_OBSERVING_PLAYED 0
4097 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4098 #define RELATION_PLAYING_MYMOVE 1
4099 #define RELATION_PLAYING_NOTMYMOVE -1
4100 #define RELATION_EXAMINING 2
4101 #define RELATION_ISOLATED_BOARD -3
4102 #define RELATION_STARTING_POSITION -4 /* FICS only */
4105 ParseBoard12 (char *string)
4107 GameMode newGameMode;
4108 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4109 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4110 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4111 char to_play, board_chars[200];
4112 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4113 char black[32], white[32];
4115 int prevMove = currentMove;
4118 int fromX, fromY, toX, toY;
4120 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4121 char *bookHit = NULL; // [HGM] book
4122 Boolean weird = FALSE, reqFlag = FALSE;
4124 fromX = fromY = toX = toY = -1;
4128 if (appData.debugMode)
4129 fprintf(debugFP, _("Parsing board: %s\n"), string);
4131 move_str[0] = NULLCHAR;
4132 elapsed_time[0] = NULLCHAR;
4133 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4135 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4136 if(string[i] == ' ') { ranks++; files = 0; }
4138 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4141 for(j = 0; j <i; j++) board_chars[j] = string[j];
4142 board_chars[i] = '\0';
4145 n = sscanf(string, PATTERN, &to_play, &double_push,
4146 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4147 &gamenum, white, black, &relation, &basetime, &increment,
4148 &white_stren, &black_stren, &white_time, &black_time,
4149 &moveNum, str, elapsed_time, move_str, &ics_flip,
4153 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4154 DisplayError(str, 0);
4158 /* Convert the move number to internal form */
4159 moveNum = (moveNum - 1) * 2;
4160 if (to_play == 'B') moveNum++;
4161 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4162 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4168 case RELATION_OBSERVING_PLAYED:
4169 case RELATION_OBSERVING_STATIC:
4170 if (gamenum == -1) {
4171 /* Old ICC buglet */
4172 relation = RELATION_OBSERVING_STATIC;
4174 newGameMode = IcsObserving;
4176 case RELATION_PLAYING_MYMOVE:
4177 case RELATION_PLAYING_NOTMYMOVE:
4179 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4180 IcsPlayingWhite : IcsPlayingBlack;
4182 case RELATION_EXAMINING:
4183 newGameMode = IcsExamining;
4185 case RELATION_ISOLATED_BOARD:
4187 /* Just display this board. If user was doing something else,
4188 we will forget about it until the next board comes. */
4189 newGameMode = IcsIdle;
4191 case RELATION_STARTING_POSITION:
4192 newGameMode = gameMode;
4196 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4197 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4198 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4200 for (k = 0; k < ranks; k++) {
4201 for (j = 0; j < files; j++)
4202 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4203 if(gameInfo.holdingsWidth > 1) {
4204 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4205 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4208 CopyBoard(partnerBoard, board);
4209 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4210 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4211 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4212 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4213 if(toSqr = strchr(str, '-')) {
4214 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4215 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4216 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4217 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4218 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4219 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4220 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4221 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4222 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4223 DisplayMessage(partnerStatus, "");
4224 partnerBoardValid = TRUE;
4228 /* Modify behavior for initial board display on move listing
4231 switch (ics_getting_history) {
4235 case H_GOT_REQ_HEADER:
4236 case H_GOT_UNREQ_HEADER:
4237 /* This is the initial position of the current game */
4238 gamenum = ics_gamenum;
4239 moveNum = 0; /* old ICS bug workaround */
4240 if (to_play == 'B') {
4241 startedFromSetupPosition = TRUE;
4242 blackPlaysFirst = TRUE;
4244 if (forwardMostMove == 0) forwardMostMove = 1;
4245 if (backwardMostMove == 0) backwardMostMove = 1;
4246 if (currentMove == 0) currentMove = 1;
4248 newGameMode = gameMode;
4249 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4251 case H_GOT_UNWANTED_HEADER:
4252 /* This is an initial board that we don't want */
4254 case H_GETTING_MOVES:
4255 /* Should not happen */
4256 DisplayError(_("Error gathering move list: extra board"), 0);
4257 ics_getting_history = H_FALSE;
4261 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4262 weird && (int)gameInfo.variant < (int)VariantShogi) {
4263 /* [HGM] We seem to have switched variant unexpectedly
4264 * Try to guess new variant from board size
4266 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4267 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4268 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4269 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4270 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4271 if(!weird) newVariant = VariantNormal;
4272 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4273 /* Get a move list just to see the header, which
4274 will tell us whether this is really bug or zh */
4275 if (ics_getting_history == H_FALSE) {
4276 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4277 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4282 /* Take action if this is the first board of a new game, or of a
4283 different game than is currently being displayed. */
4284 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4285 relation == RELATION_ISOLATED_BOARD) {
4287 /* Forget the old game and get the history (if any) of the new one */
4288 if (gameMode != BeginningOfGame) {
4292 if (appData.autoRaiseBoard) BoardToTop();
4294 if (gamenum == -1) {
4295 newGameMode = IcsIdle;
4296 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4297 appData.getMoveList && !reqFlag) {
4298 /* Need to get game history */
4299 ics_getting_history = H_REQUESTED;
4300 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4304 /* Initially flip the board to have black on the bottom if playing
4305 black or if the ICS flip flag is set, but let the user change
4306 it with the Flip View button. */
4307 flipView = appData.autoFlipView ?
4308 (newGameMode == IcsPlayingBlack) || ics_flip :
4311 /* Done with values from previous mode; copy in new ones */
4312 gameMode = newGameMode;
4314 ics_gamenum = gamenum;
4315 if (gamenum == gs_gamenum) {
4316 int klen = strlen(gs_kind);
4317 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4318 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4319 gameInfo.event = StrSave(str);
4321 gameInfo.event = StrSave("ICS game");
4323 gameInfo.site = StrSave(appData.icsHost);
4324 gameInfo.date = PGNDate();
4325 gameInfo.round = StrSave("-");
4326 gameInfo.white = StrSave(white);
4327 gameInfo.black = StrSave(black);
4328 timeControl = basetime * 60 * 1000;
4330 timeIncrement = increment * 1000;
4331 movesPerSession = 0;
4332 gameInfo.timeControl = TimeControlTagValue();
4333 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4334 if (appData.debugMode) {
4335 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4336 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4337 setbuf(debugFP, NULL);
4340 gameInfo.outOfBook = NULL;
4342 /* Do we have the ratings? */
4343 if (strcmp(player1Name, white) == 0 &&
4344 strcmp(player2Name, black) == 0) {
4345 if (appData.debugMode)
4346 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4347 player1Rating, player2Rating);
4348 gameInfo.whiteRating = player1Rating;
4349 gameInfo.blackRating = player2Rating;
4350 } else if (strcmp(player2Name, white) == 0 &&
4351 strcmp(player1Name, black) == 0) {
4352 if (appData.debugMode)
4353 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4354 player2Rating, player1Rating);
4355 gameInfo.whiteRating = player2Rating;
4356 gameInfo.blackRating = player1Rating;
4358 player1Name[0] = player2Name[0] = NULLCHAR;
4360 /* Silence shouts if requested */
4361 if (appData.quietPlay &&
4362 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4363 SendToICS(ics_prefix);
4364 SendToICS("set shout 0\n");
4368 /* Deal with midgame name changes */
4370 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4371 if (gameInfo.white) free(gameInfo.white);
4372 gameInfo.white = StrSave(white);
4374 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4375 if (gameInfo.black) free(gameInfo.black);
4376 gameInfo.black = StrSave(black);
4380 /* Throw away game result if anything actually changes in examine mode */
4381 if (gameMode == IcsExamining && !newGame) {
4382 gameInfo.result = GameUnfinished;
4383 if (gameInfo.resultDetails != NULL) {
4384 free(gameInfo.resultDetails);
4385 gameInfo.resultDetails = NULL;
4389 /* In pausing && IcsExamining mode, we ignore boards coming
4390 in if they are in a different variation than we are. */
4391 if (pauseExamInvalid) return;
4392 if (pausing && gameMode == IcsExamining) {
4393 if (moveNum <= pauseExamForwardMostMove) {
4394 pauseExamInvalid = TRUE;
4395 forwardMostMove = pauseExamForwardMostMove;
4400 if (appData.debugMode) {
4401 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4403 /* Parse the board */
4404 for (k = 0; k < ranks; k++) {
4405 for (j = 0; j < files; j++)
4406 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4407 if(gameInfo.holdingsWidth > 1) {
4408 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4409 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4412 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4413 board[5][BOARD_RGHT+1] = WhiteAngel;
4414 board[6][BOARD_RGHT+1] = WhiteMarshall;
4415 board[1][0] = BlackMarshall;
4416 board[2][0] = BlackAngel;
4417 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4419 CopyBoard(boards[moveNum], board);
4420 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4422 startedFromSetupPosition =
4423 !CompareBoards(board, initialPosition);
4424 if(startedFromSetupPosition)
4425 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4428 /* [HGM] Set castling rights. Take the outermost Rooks,
4429 to make it also work for FRC opening positions. Note that board12
4430 is really defective for later FRC positions, as it has no way to
4431 indicate which Rook can castle if they are on the same side of King.
4432 For the initial position we grant rights to the outermost Rooks,
4433 and remember thos rights, and we then copy them on positions
4434 later in an FRC game. This means WB might not recognize castlings with
4435 Rooks that have moved back to their original position as illegal,
4436 but in ICS mode that is not its job anyway.
4438 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4439 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4441 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442 if(board[0][i] == WhiteRook) j = i;
4443 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445 if(board[0][i] == WhiteRook) j = i;
4446 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4448 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4451 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4452 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4455 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4456 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4458 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4459 if(board[BOARD_HEIGHT-1][k] == bKing)
4460 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4461 if(gameInfo.variant == VariantTwoKings) {
4462 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4463 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4464 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4467 r = boards[moveNum][CASTLING][0] = initialRights[0];
4468 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4469 r = boards[moveNum][CASTLING][1] = initialRights[1];
4470 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4471 r = boards[moveNum][CASTLING][3] = initialRights[3];
4472 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4473 r = boards[moveNum][CASTLING][4] = initialRights[4];
4474 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4475 /* wildcastle kludge: always assume King has rights */
4476 r = boards[moveNum][CASTLING][2] = initialRights[2];
4477 r = boards[moveNum][CASTLING][5] = initialRights[5];
4479 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4480 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4483 if (ics_getting_history == H_GOT_REQ_HEADER ||
4484 ics_getting_history == H_GOT_UNREQ_HEADER) {
4485 /* This was an initial position from a move list, not
4486 the current position */
4490 /* Update currentMove and known move number limits */
4491 newMove = newGame || moveNum > forwardMostMove;
4494 forwardMostMove = backwardMostMove = currentMove = moveNum;
4495 if (gameMode == IcsExamining && moveNum == 0) {
4496 /* Workaround for ICS limitation: we are not told the wild
4497 type when starting to examine a game. But if we ask for
4498 the move list, the move list header will tell us */
4499 ics_getting_history = H_REQUESTED;
4500 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4503 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4504 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4506 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4507 /* [HGM] applied this also to an engine that is silently watching */
4508 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4509 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4510 gameInfo.variant == currentlyInitializedVariant) {
4511 takeback = forwardMostMove - moveNum;
4512 for (i = 0; i < takeback; i++) {
4513 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4514 SendToProgram("undo\n", &first);
4519 forwardMostMove = moveNum;
4520 if (!pausing || currentMove > forwardMostMove)
4521 currentMove = forwardMostMove;
4523 /* New part of history that is not contiguous with old part */
4524 if (pausing && gameMode == IcsExamining) {
4525 pauseExamInvalid = TRUE;
4526 forwardMostMove = pauseExamForwardMostMove;
4529 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4531 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4532 // [HGM] when we will receive the move list we now request, it will be
4533 // fed to the engine from the first move on. So if the engine is not
4534 // in the initial position now, bring it there.
4535 InitChessProgram(&first, 0);
4538 ics_getting_history = H_REQUESTED;
4539 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542 forwardMostMove = backwardMostMove = currentMove = moveNum;
4545 /* Update the clocks */
4546 if (strchr(elapsed_time, '.')) {
4548 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4549 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4551 /* Time is in seconds */
4552 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4553 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4558 if (appData.zippyPlay && newGame &&
4559 gameMode != IcsObserving && gameMode != IcsIdle &&
4560 gameMode != IcsExamining)
4561 ZippyFirstBoard(moveNum, basetime, increment);
4564 /* Put the move on the move list, first converting
4565 to canonical algebraic form. */
4567 if (appData.debugMode) {
4568 if (appData.debugMode) { int f = forwardMostMove;
4569 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4570 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4571 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4573 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4574 fprintf(debugFP, "moveNum = %d\n", moveNum);
4575 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4576 setbuf(debugFP, NULL);
4578 if (moveNum <= backwardMostMove) {
4579 /* We don't know what the board looked like before
4581 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4582 strcat(parseList[moveNum - 1], " ");
4583 strcat(parseList[moveNum - 1], elapsed_time);
4584 moveList[moveNum - 1][0] = NULLCHAR;
4585 } else if (strcmp(move_str, "none") == 0) {
4586 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4587 /* Again, we don't know what the board looked like;
4588 this is really the start of the game. */
4589 parseList[moveNum - 1][0] = NULLCHAR;
4590 moveList[moveNum - 1][0] = NULLCHAR;
4591 backwardMostMove = moveNum;
4592 startedFromSetupPosition = TRUE;
4593 fromX = fromY = toX = toY = -1;
4595 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4596 // So we parse the long-algebraic move string in stead of the SAN move
4597 int valid; char buf[MSG_SIZ], *prom;
4599 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4600 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4601 // str looks something like "Q/a1-a2"; kill the slash
4603 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4604 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4605 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4606 strcat(buf, prom); // long move lacks promo specification!
4607 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4608 if(appData.debugMode)
4609 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4610 safeStrCpy(move_str, buf, MSG_SIZ);
4612 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4613 &fromX, &fromY, &toX, &toY, &promoChar)
4614 || ParseOneMove(buf, moveNum - 1, &moveType,
4615 &fromX, &fromY, &toX, &toY, &promoChar);
4616 // end of long SAN patch
4618 (void) CoordsToAlgebraic(boards[moveNum - 1],
4619 PosFlags(moveNum - 1),
4620 fromY, fromX, toY, toX, promoChar,
4621 parseList[moveNum-1]);
4622 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4628 if(gameInfo.variant != VariantShogi)
4629 strcat(parseList[moveNum - 1], "+");
4632 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4633 strcat(parseList[moveNum - 1], "#");
4636 strcat(parseList[moveNum - 1], " ");
4637 strcat(parseList[moveNum - 1], elapsed_time);
4638 /* currentMoveString is set as a side-effect of ParseOneMove */
4639 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4640 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4641 strcat(moveList[moveNum - 1], "\n");
4643 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4644 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4645 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4646 ChessSquare old, new = boards[moveNum][k][j];
4647 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4648 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4649 if(old == new) continue;
4650 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4651 else if(new == WhiteWazir || new == BlackWazir) {
4652 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4653 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4654 else boards[moveNum][k][j] = old; // preserve type of Gold
4655 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4656 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4659 /* Move from ICS was illegal!? Punt. */
4660 if (appData.debugMode) {
4661 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4662 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4664 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4665 strcat(parseList[moveNum - 1], " ");
4666 strcat(parseList[moveNum - 1], elapsed_time);
4667 moveList[moveNum - 1][0] = NULLCHAR;
4668 fromX = fromY = toX = toY = -1;
4671 if (appData.debugMode) {
4672 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4673 setbuf(debugFP, NULL);
4677 /* Send move to chess program (BEFORE animating it). */
4678 if (appData.zippyPlay && !newGame && newMove &&
4679 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4681 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4682 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4683 if (moveList[moveNum - 1][0] == NULLCHAR) {
4684 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4686 DisplayError(str, 0);
4688 if (first.sendTime) {
4689 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4691 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4692 if (firstMove && !bookHit) {
4694 if (first.useColors) {
4695 SendToProgram(gameMode == IcsPlayingWhite ?
4697 "black\ngo\n", &first);
4699 SendToProgram("go\n", &first);
4701 first.maybeThinking = TRUE;
4704 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4705 if (moveList[moveNum - 1][0] == NULLCHAR) {
4706 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4707 DisplayError(str, 0);
4709 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4710 SendMoveToProgram(moveNum - 1, &first);
4717 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4718 /* If move comes from a remote source, animate it. If it
4719 isn't remote, it will have already been animated. */
4720 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4721 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4723 if (!pausing && appData.highlightLastMove) {
4724 SetHighlights(fromX, fromY, toX, toY);
4728 /* Start the clocks */
4729 whiteFlag = blackFlag = FALSE;
4730 appData.clockMode = !(basetime == 0 && increment == 0);
4732 ics_clock_paused = TRUE;
4734 } else if (ticking == 1) {
4735 ics_clock_paused = FALSE;
4737 if (gameMode == IcsIdle ||
4738 relation == RELATION_OBSERVING_STATIC ||
4739 relation == RELATION_EXAMINING ||
4741 DisplayBothClocks();
4745 /* Display opponents and material strengths */
4746 if (gameInfo.variant != VariantBughouse &&
4747 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4748 if (tinyLayout || smallLayout) {
4749 if(gameInfo.variant == VariantNormal)
4750 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4751 gameInfo.white, white_stren, gameInfo.black, black_stren,
4752 basetime, increment);
4754 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4755 gameInfo.white, white_stren, gameInfo.black, black_stren,
4756 basetime, increment, (int) gameInfo.variant);
4758 if(gameInfo.variant == VariantNormal)
4759 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4760 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4761 basetime, increment);
4763 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4764 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4765 basetime, increment, VariantName(gameInfo.variant));
4768 if (appData.debugMode) {
4769 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4774 /* Display the board */
4775 if (!pausing && !appData.noGUI) {
4777 if (appData.premove)
4779 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4780 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4781 ClearPremoveHighlights();
4783 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4784 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4785 DrawPosition(j, boards[currentMove]);
4787 DisplayMove(moveNum - 1);
4788 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4789 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4790 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4791 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4795 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4797 if(bookHit) { // [HGM] book: simulate book reply
4798 static char bookMove[MSG_SIZ]; // a bit generous?
4800 programStats.nodes = programStats.depth = programStats.time =
4801 programStats.score = programStats.got_only_move = 0;
4802 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4804 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4805 strcat(bookMove, bookHit);
4806 HandleMachineMove(bookMove, &first);
4815 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4816 ics_getting_history = H_REQUESTED;
4817 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4823 AnalysisPeriodicEvent (int force)
4825 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4826 && !force) || !appData.periodicUpdates)
4829 /* Send . command to Crafty to collect stats */
4830 SendToProgram(".\n", &first);
4832 /* Don't send another until we get a response (this makes
4833 us stop sending to old Crafty's which don't understand
4834 the "." command (sending illegal cmds resets node count & time,
4835 which looks bad)) */
4836 programStats.ok_to_send = 0;
4840 ics_update_width (int new_width)
4842 ics_printf("set width %d\n", new_width);
4846 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4850 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4851 // null move in variant where engine does not understand it (for analysis purposes)
4852 SendBoard(cps, moveNum + 1); // send position after move in stead.
4855 if (cps->useUsermove) {
4856 SendToProgram("usermove ", cps);
4860 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4861 int len = space - parseList[moveNum];
4862 memcpy(buf, parseList[moveNum], len);
4864 buf[len] = NULLCHAR;
4866 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4868 SendToProgram(buf, cps);
4870 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4871 AlphaRank(moveList[moveNum], 4);
4872 SendToProgram(moveList[moveNum], cps);
4873 AlphaRank(moveList[moveNum], 4); // and back
4875 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4876 * the engine. It would be nice to have a better way to identify castle
4878 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4879 && cps->useOOCastle) {
4880 int fromX = moveList[moveNum][0] - AAA;
4881 int fromY = moveList[moveNum][1] - ONE;
4882 int toX = moveList[moveNum][2] - AAA;
4883 int toY = moveList[moveNum][3] - ONE;
4884 if((boards[moveNum][fromY][fromX] == WhiteKing
4885 && boards[moveNum][toY][toX] == WhiteRook)
4886 || (boards[moveNum][fromY][fromX] == BlackKing
4887 && boards[moveNum][toY][toX] == BlackRook)) {
4888 if(toX > fromX) SendToProgram("O-O\n", cps);
4889 else SendToProgram("O-O-O\n", cps);
4891 else SendToProgram(moveList[moveNum], cps);
4893 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4894 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4895 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4896 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4897 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4899 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4900 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4901 SendToProgram(buf, cps);
4903 else SendToProgram(moveList[moveNum], cps);
4904 /* End of additions by Tord */
4907 /* [HGM] setting up the opening has brought engine in force mode! */
4908 /* Send 'go' if we are in a mode where machine should play. */
4909 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4910 (gameMode == TwoMachinesPlay ||
4912 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4914 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4915 SendToProgram("go\n", cps);
4916 if (appData.debugMode) {
4917 fprintf(debugFP, "(extra)\n");
4920 setboardSpoiledMachineBlack = 0;
4924 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4926 char user_move[MSG_SIZ];
4929 if(gameInfo.variant == VariantSChess && promoChar) {
4930 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4931 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4932 } else suffix[0] = NULLCHAR;
4936 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4937 (int)moveType, fromX, fromY, toX, toY);
4938 DisplayError(user_move + strlen("say "), 0);
4940 case WhiteKingSideCastle:
4941 case BlackKingSideCastle:
4942 case WhiteQueenSideCastleWild:
4943 case BlackQueenSideCastleWild:
4945 case WhiteHSideCastleFR:
4946 case BlackHSideCastleFR:
4948 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4950 case WhiteQueenSideCastle:
4951 case BlackQueenSideCastle:
4952 case WhiteKingSideCastleWild:
4953 case BlackKingSideCastleWild:
4955 case WhiteASideCastleFR:
4956 case BlackASideCastleFR:
4958 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4960 case WhiteNonPromotion:
4961 case BlackNonPromotion:
4962 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4964 case WhitePromotion:
4965 case BlackPromotion:
4966 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4967 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4968 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4969 PieceToChar(WhiteFerz));
4970 else if(gameInfo.variant == VariantGreat)
4971 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4972 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973 PieceToChar(WhiteMan));
4975 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4976 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4982 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4983 ToUpper(PieceToChar((ChessSquare) fromX)),
4984 AAA + toX, ONE + toY);
4986 case IllegalMove: /* could be a variant we don't quite understand */
4987 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4989 case WhiteCapturesEnPassant:
4990 case BlackCapturesEnPassant:
4991 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4992 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4995 SendToICS(user_move);
4996 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4997 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5002 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5003 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5004 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5005 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5006 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5009 if(gameMode != IcsExamining) { // is this ever not the case?
5010 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5012 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5013 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5014 } else { // on FICS we must first go to general examine mode
5015 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5017 if(gameInfo.variant != VariantNormal) {
5018 // try figure out wild number, as xboard names are not always valid on ICS
5019 for(i=1; i<=36; i++) {
5020 snprintf(buf, MSG_SIZ, "wild/%d", i);
5021 if(StringToVariant(buf) == gameInfo.variant) break;
5023 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5024 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5025 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5026 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5027 SendToICS(ics_prefix);
5029 if(startedFromSetupPosition || backwardMostMove != 0) {
5030 fen = PositionToFEN(backwardMostMove, NULL);
5031 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5032 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5034 } else { // FICS: everything has to set by separate bsetup commands
5035 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5036 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5038 if(!WhiteOnMove(backwardMostMove)) {
5039 SendToICS("bsetup tomove black\n");
5041 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5042 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5044 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5045 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5047 i = boards[backwardMostMove][EP_STATUS];
5048 if(i >= 0) { // set e.p.
5049 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5055 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5056 SendToICS("bsetup done\n"); // switch to normal examining.
5058 for(i = backwardMostMove; i<last; i++) {
5060 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5063 SendToICS(ics_prefix);
5064 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5068 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5070 if (rf == DROP_RANK) {
5071 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5072 sprintf(move, "%c@%c%c\n",
5073 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5075 if (promoChar == 'x' || promoChar == NULLCHAR) {
5076 sprintf(move, "%c%c%c%c\n",
5077 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5079 sprintf(move, "%c%c%c%c%c\n",
5080 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5086 ProcessICSInitScript (FILE *f)
5090 while (fgets(buf, MSG_SIZ, f)) {
5091 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5098 static int lastX, lastY, selectFlag, dragging;
5103 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5104 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5105 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5106 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5107 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5108 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5111 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5112 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5113 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5114 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5115 if(!step) step = -1;
5116 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5117 appData.testLegality && (promoSweep == king ||
5118 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5119 ChangeDragPiece(promoSweep);
5123 PromoScroll (int x, int y)
5127 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5128 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5129 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5130 if(!step) return FALSE;
5131 lastX = x; lastY = y;
5132 if((promoSweep < BlackPawn) == flipView) step = -step;
5133 if(step > 0) selectFlag = 1;
5134 if(!selectFlag) Sweep(step);
5139 NextPiece (int step)
5141 ChessSquare piece = boards[currentMove][toY][toX];
5144 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5145 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5146 if(!step) step = -1;
5147 } while(PieceToChar(pieceSweep) == '.');
5148 boards[currentMove][toY][toX] = pieceSweep;
5149 DrawPosition(FALSE, boards[currentMove]);
5150 boards[currentMove][toY][toX] = piece;
5152 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5154 AlphaRank (char *move, int n)
5156 // char *p = move, c; int x, y;
5158 if (appData.debugMode) {
5159 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5163 move[2]>='0' && move[2]<='9' &&
5164 move[3]>='a' && move[3]<='x' ) {
5166 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5167 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5169 if(move[0]>='0' && move[0]<='9' &&
5170 move[1]>='a' && move[1]<='x' &&
5171 move[2]>='0' && move[2]<='9' &&
5172 move[3]>='a' && move[3]<='x' ) {
5173 /* input move, Shogi -> normal */
5174 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5175 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5176 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5177 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5180 move[3]>='0' && move[3]<='9' &&
5181 move[2]>='a' && move[2]<='x' ) {
5183 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5184 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5187 move[0]>='a' && move[0]<='x' &&
5188 move[3]>='0' && move[3]<='9' &&
5189 move[2]>='a' && move[2]<='x' ) {
5190 /* output move, normal -> Shogi */
5191 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5192 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5193 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5194 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5195 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5197 if (appData.debugMode) {
5198 fprintf(debugFP, " out = '%s'\n", move);
5202 char yy_textstr[8000];
5204 /* Parser for moves from gnuchess, ICS, or user typein box */
5206 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5208 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5210 switch (*moveType) {
5211 case WhitePromotion:
5212 case BlackPromotion:
5213 case WhiteNonPromotion:
5214 case BlackNonPromotion:
5216 case WhiteCapturesEnPassant:
5217 case BlackCapturesEnPassant:
5218 case WhiteKingSideCastle:
5219 case WhiteQueenSideCastle:
5220 case BlackKingSideCastle:
5221 case BlackQueenSideCastle:
5222 case WhiteKingSideCastleWild:
5223 case WhiteQueenSideCastleWild:
5224 case BlackKingSideCastleWild:
5225 case BlackQueenSideCastleWild:
5226 /* Code added by Tord: */
5227 case WhiteHSideCastleFR:
5228 case WhiteASideCastleFR:
5229 case BlackHSideCastleFR:
5230 case BlackASideCastleFR:
5231 /* End of code added by Tord */
5232 case IllegalMove: /* bug or odd chess variant */
5233 *fromX = currentMoveString[0] - AAA;
5234 *fromY = currentMoveString[1] - ONE;
5235 *toX = currentMoveString[2] - AAA;
5236 *toY = currentMoveString[3] - ONE;
5237 *promoChar = currentMoveString[4];
5238 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5239 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5240 if (appData.debugMode) {
5241 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5243 *fromX = *fromY = *toX = *toY = 0;
5246 if (appData.testLegality) {
5247 return (*moveType != IllegalMove);
5249 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5250 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5255 *fromX = *moveType == WhiteDrop ?
5256 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5257 (int) CharToPiece(ToLower(currentMoveString[0]));
5259 *toX = currentMoveString[2] - AAA;
5260 *toY = currentMoveString[3] - ONE;
5261 *promoChar = NULLCHAR;
5265 case ImpossibleMove:
5275 if (appData.debugMode) {
5276 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5279 *fromX = *fromY = *toX = *toY = 0;
5280 *promoChar = NULLCHAR;
5285 Boolean pushed = FALSE;
5286 char *lastParseAttempt;
5289 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5290 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5291 int fromX, fromY, toX, toY; char promoChar;
5296 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5297 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5300 endPV = forwardMostMove;
5302 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5303 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5304 lastParseAttempt = pv;
5305 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5306 if(appData.debugMode){
5307 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5309 if(!valid && nr == 0 &&
5310 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5311 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5312 // Hande case where played move is different from leading PV move
5313 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5314 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5315 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5316 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5317 endPV += 2; // if position different, keep this
5318 moveList[endPV-1][0] = fromX + AAA;
5319 moveList[endPV-1][1] = fromY + ONE;
5320 moveList[endPV-1][2] = toX + AAA;
5321 moveList[endPV-1][3] = toY + ONE;
5322 parseList[endPV-1][0] = NULLCHAR;
5323 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5326 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5327 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5328 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5329 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5330 valid++; // allow comments in PV
5334 if(endPV+1 > framePtr) break; // no space, truncate
5337 CopyBoard(boards[endPV], boards[endPV-1]);
5338 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5339 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5340 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5341 CoordsToAlgebraic(boards[endPV - 1],
5342 PosFlags(endPV - 1),
5343 fromY, fromX, toY, toX, promoChar,
5344 parseList[endPV - 1]);
5346 if(atEnd == 2) return; // used hidden, for PV conversion
5347 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5348 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5349 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5350 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5351 DrawPosition(TRUE, boards[currentMove]);
5355 MultiPV (ChessProgramState *cps)
5356 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5358 for(i=0; i<cps->nrOptions; i++)
5359 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5365 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5367 int startPV, multi, lineStart, origIndex = index;
5368 char *p, buf2[MSG_SIZ];
5370 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5371 lastX = x; lastY = y;
5372 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5373 lineStart = startPV = index;
5374 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5375 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5377 do{ while(buf[index] && buf[index] != '\n') index++;
5378 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5380 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5381 int n = first.option[multi].value;
5382 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5383 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5384 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5385 first.option[multi].value = n;
5389 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5390 *start = startPV; *end = index-1;
5397 static char buf[10*MSG_SIZ];
5398 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5400 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5401 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5402 for(i = forwardMostMove; i<endPV; i++){
5403 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5404 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5407 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5408 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5414 LoadPV (int x, int y)
5415 { // called on right mouse click to load PV
5416 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5417 lastX = x; lastY = y;
5418 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5425 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5426 if(endPV < 0) return;
5428 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5429 Boolean saveAnimate = appData.animate;
5431 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5432 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5433 } else storedGames--; // abandon shelved tail of original game
5436 forwardMostMove = currentMove;
5437 currentMove = oldFMM;
5438 appData.animate = FALSE;
5439 ToNrEvent(forwardMostMove);
5440 appData.animate = saveAnimate;
5442 currentMove = forwardMostMove;
5443 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5444 ClearPremoveHighlights();
5445 DrawPosition(TRUE, boards[currentMove]);
5449 MovePV (int x, int y, int h)
5450 { // step through PV based on mouse coordinates (called on mouse move)
5451 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5453 // we must somehow check if right button is still down (might be released off board!)
5454 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5455 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5456 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5458 lastX = x; lastY = y;
5460 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5461 if(endPV < 0) return;
5462 if(y < margin) step = 1; else
5463 if(y > h - margin) step = -1;
5464 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5465 currentMove += step;
5466 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5467 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5468 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5469 DrawPosition(FALSE, boards[currentMove]);
5473 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5474 // All positions will have equal probability, but the current method will not provide a unique
5475 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5481 int piecesLeft[(int)BlackPawn];
5482 int seed, nrOfShuffles;
5485 GetPositionNumber ()
5486 { // sets global variable seed
5489 seed = appData.defaultFrcPosition;
5490 if(seed < 0) { // randomize based on time for negative FRC position numbers
5491 for(i=0; i<50; i++) seed += random();
5492 seed = random() ^ random() >> 8 ^ random() << 8;
5493 if(seed<0) seed = -seed;
5498 put (Board board, int pieceType, int rank, int n, int shade)
5499 // put the piece on the (n-1)-th empty squares of the given shade
5503 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5504 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5505 board[rank][i] = (ChessSquare) pieceType;
5506 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5508 piecesLeft[pieceType]--;
5517 AddOnePiece (Board board, int pieceType, int rank, int shade)
5518 // calculate where the next piece goes, (any empty square), and put it there
5522 i = seed % squaresLeft[shade];
5523 nrOfShuffles *= squaresLeft[shade];
5524 seed /= squaresLeft[shade];
5525 put(board, pieceType, rank, i, shade);
5529 AddTwoPieces (Board board, int pieceType, int rank)
5530 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5532 int i, n=squaresLeft[ANY], j=n-1, k;
5534 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5535 i = seed % k; // pick one
5538 while(i >= j) i -= j--;
5539 j = n - 1 - j; i += j;
5540 put(board, pieceType, rank, j, ANY);
5541 put(board, pieceType, rank, i, ANY);
5545 SetUpShuffle (Board board, int number)
5549 GetPositionNumber(); nrOfShuffles = 1;
5551 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5552 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5553 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5555 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5557 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5558 p = (int) board[0][i];
5559 if(p < (int) BlackPawn) piecesLeft[p] ++;
5560 board[0][i] = EmptySquare;
5563 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5564 // shuffles restricted to allow normal castling put KRR first
5565 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5566 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5567 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5568 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5569 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5570 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5571 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5572 put(board, WhiteRook, 0, 0, ANY);
5573 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5576 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5577 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5578 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5579 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5580 while(piecesLeft[p] >= 2) {
5581 AddOnePiece(board, p, 0, LITE);
5582 AddOnePiece(board, p, 0, DARK);
5584 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5587 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5588 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5589 // but we leave King and Rooks for last, to possibly obey FRC restriction
5590 if(p == (int)WhiteRook) continue;
5591 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5592 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5595 // now everything is placed, except perhaps King (Unicorn) and Rooks
5597 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5598 // Last King gets castling rights
5599 while(piecesLeft[(int)WhiteUnicorn]) {
5600 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5601 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5604 while(piecesLeft[(int)WhiteKing]) {
5605 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5606 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5611 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5612 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5615 // Only Rooks can be left; simply place them all
5616 while(piecesLeft[(int)WhiteRook]) {
5617 i = put(board, WhiteRook, 0, 0, ANY);
5618 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5621 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5623 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5626 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5627 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5630 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5634 SetCharTable (char *table, const char * map)
5635 /* [HGM] moved here from winboard.c because of its general usefulness */
5636 /* Basically a safe strcpy that uses the last character as King */
5638 int result = FALSE; int NrPieces;
5640 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5641 && NrPieces >= 12 && !(NrPieces&1)) {
5642 int i; /* [HGM] Accept even length from 12 to 34 */
5644 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5645 for( i=0; i<NrPieces/2-1; i++ ) {
5647 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5649 table[(int) WhiteKing] = map[NrPieces/2-1];
5650 table[(int) BlackKing] = map[NrPieces-1];
5659 Prelude (Board board)
5660 { // [HGM] superchess: random selection of exo-pieces
5661 int i, j, k; ChessSquare p;
5662 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5664 GetPositionNumber(); // use FRC position number
5666 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5667 SetCharTable(pieceToChar, appData.pieceToCharTable);
5668 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5669 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5672 j = seed%4; seed /= 4;
5673 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5674 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5675 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5676 j = seed%3 + (seed%3 >= j); seed /= 3;
5677 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5679 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5680 j = seed%3; seed /= 3;
5681 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5682 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5683 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5684 j = seed%2 + (seed%2 >= j); seed /= 2;
5685 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5687 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5688 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5689 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5690 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5691 put(board, exoPieces[0], 0, 0, ANY);
5692 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5696 InitPosition (int redraw)
5698 ChessSquare (* pieces)[BOARD_FILES];
5699 int i, j, pawnRow, overrule,
5700 oldx = gameInfo.boardWidth,
5701 oldy = gameInfo.boardHeight,
5702 oldh = gameInfo.holdingsWidth;
5705 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5707 /* [AS] Initialize pv info list [HGM] and game status */
5709 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5710 pvInfoList[i].depth = 0;
5711 boards[i][EP_STATUS] = EP_NONE;
5712 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5715 initialRulePlies = 0; /* 50-move counter start */
5717 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5718 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5722 /* [HGM] logic here is completely changed. In stead of full positions */
5723 /* the initialized data only consist of the two backranks. The switch */
5724 /* selects which one we will use, which is than copied to the Board */
5725 /* initialPosition, which for the rest is initialized by Pawns and */
5726 /* empty squares. This initial position is then copied to boards[0], */
5727 /* possibly after shuffling, so that it remains available. */
5729 gameInfo.holdingsWidth = 0; /* default board sizes */
5730 gameInfo.boardWidth = 8;
5731 gameInfo.boardHeight = 8;
5732 gameInfo.holdingsSize = 0;
5733 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5734 for(i=0; i<BOARD_FILES-2; i++)
5735 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5736 initialPosition[EP_STATUS] = EP_NONE;
5737 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5738 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5739 SetCharTable(pieceNickName, appData.pieceNickNames);
5740 else SetCharTable(pieceNickName, "............");
5743 switch (gameInfo.variant) {
5744 case VariantFischeRandom:
5745 shuffleOpenings = TRUE;
5748 case VariantShatranj:
5749 pieces = ShatranjArray;
5750 nrCastlingRights = 0;
5751 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5754 pieces = makrukArray;
5755 nrCastlingRights = 0;
5756 startedFromSetupPosition = TRUE;
5757 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5759 case VariantTwoKings:
5760 pieces = twoKingsArray;
5763 pieces = GrandArray;
5764 nrCastlingRights = 0;
5765 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5766 gameInfo.boardWidth = 10;
5767 gameInfo.boardHeight = 10;
5768 gameInfo.holdingsSize = 7;
5770 case VariantCapaRandom:
5771 shuffleOpenings = TRUE;
5772 case VariantCapablanca:
5773 pieces = CapablancaArray;
5774 gameInfo.boardWidth = 10;
5775 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5778 pieces = GothicArray;
5779 gameInfo.boardWidth = 10;
5780 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5783 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5784 gameInfo.holdingsSize = 7;
5787 pieces = JanusArray;
5788 gameInfo.boardWidth = 10;
5789 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5790 nrCastlingRights = 6;
5791 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5792 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5793 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5794 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5795 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5796 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5799 pieces = FalconArray;
5800 gameInfo.boardWidth = 10;
5801 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5803 case VariantXiangqi:
5804 pieces = XiangqiArray;
5805 gameInfo.boardWidth = 9;
5806 gameInfo.boardHeight = 10;
5807 nrCastlingRights = 0;
5808 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5811 pieces = ShogiArray;
5812 gameInfo.boardWidth = 9;
5813 gameInfo.boardHeight = 9;
5814 gameInfo.holdingsSize = 7;
5815 nrCastlingRights = 0;
5816 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5818 case VariantCourier:
5819 pieces = CourierArray;
5820 gameInfo.boardWidth = 12;
5821 nrCastlingRights = 0;
5822 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5824 case VariantKnightmate:
5825 pieces = KnightmateArray;
5826 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5828 case VariantSpartan:
5829 pieces = SpartanArray;
5830 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5833 pieces = fairyArray;
5834 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5837 pieces = GreatArray;
5838 gameInfo.boardWidth = 10;
5839 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5840 gameInfo.holdingsSize = 8;
5844 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5845 gameInfo.holdingsSize = 8;
5846 startedFromSetupPosition = TRUE;
5848 case VariantCrazyhouse:
5849 case VariantBughouse:
5851 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5852 gameInfo.holdingsSize = 5;
5854 case VariantWildCastle:
5856 /* !!?shuffle with kings guaranteed to be on d or e file */
5857 shuffleOpenings = 1;
5859 case VariantNoCastle:
5861 nrCastlingRights = 0;
5862 /* !!?unconstrained back-rank shuffle */
5863 shuffleOpenings = 1;
5868 if(appData.NrFiles >= 0) {
5869 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5870 gameInfo.boardWidth = appData.NrFiles;
5872 if(appData.NrRanks >= 0) {
5873 gameInfo.boardHeight = appData.NrRanks;
5875 if(appData.holdingsSize >= 0) {
5876 i = appData.holdingsSize;
5877 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5878 gameInfo.holdingsSize = i;
5880 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5881 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5882 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5884 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5885 if(pawnRow < 1) pawnRow = 1;
5886 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5888 /* User pieceToChar list overrules defaults */
5889 if(appData.pieceToCharTable != NULL)
5890 SetCharTable(pieceToChar, appData.pieceToCharTable);
5892 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5894 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5895 s = (ChessSquare) 0; /* account holding counts in guard band */
5896 for( i=0; i<BOARD_HEIGHT; i++ )
5897 initialPosition[i][j] = s;
5899 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5900 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5901 initialPosition[pawnRow][j] = WhitePawn;
5902 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5903 if(gameInfo.variant == VariantXiangqi) {
5905 initialPosition[pawnRow][j] =
5906 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5907 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5908 initialPosition[2][j] = WhiteCannon;
5909 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5913 if(gameInfo.variant == VariantGrand) {
5914 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5915 initialPosition[0][j] = WhiteRook;
5916 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5919 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5921 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5924 initialPosition[1][j] = WhiteBishop;
5925 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5927 initialPosition[1][j] = WhiteRook;
5928 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5931 if( nrCastlingRights == -1) {
5932 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5933 /* This sets default castling rights from none to normal corners */
5934 /* Variants with other castling rights must set them themselves above */
5935 nrCastlingRights = 6;
5937 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5938 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5939 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5940 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5941 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5942 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5945 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5946 if(gameInfo.variant == VariantGreat) { // promotion commoners
5947 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5948 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5949 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5950 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5952 if( gameInfo.variant == VariantSChess ) {
5953 initialPosition[1][0] = BlackMarshall;
5954 initialPosition[2][0] = BlackAngel;
5955 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5956 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5957 initialPosition[1][1] = initialPosition[2][1] =
5958 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5960 if (appData.debugMode) {
5961 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5963 if(shuffleOpenings) {
5964 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5965 startedFromSetupPosition = TRUE;
5967 if(startedFromPositionFile) {
5968 /* [HGM] loadPos: use PositionFile for every new game */
5969 CopyBoard(initialPosition, filePosition);
5970 for(i=0; i<nrCastlingRights; i++)
5971 initialRights[i] = filePosition[CASTLING][i];
5972 startedFromSetupPosition = TRUE;
5975 CopyBoard(boards[0], initialPosition);
5977 if(oldx != gameInfo.boardWidth ||
5978 oldy != gameInfo.boardHeight ||
5979 oldv != gameInfo.variant ||
5980 oldh != gameInfo.holdingsWidth
5982 InitDrawingSizes(-2 ,0);
5984 oldv = gameInfo.variant;
5986 DrawPosition(TRUE, boards[currentMove]);
5990 SendBoard (ChessProgramState *cps, int moveNum)
5992 char message[MSG_SIZ];
5994 if (cps->useSetboard) {
5995 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5996 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5997 SendToProgram(message, cps);
6002 int i, j, left=0, right=BOARD_WIDTH;
6003 /* Kludge to set black to move, avoiding the troublesome and now
6004 * deprecated "black" command.
6006 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6007 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6009 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6011 SendToProgram("edit\n", cps);
6012 SendToProgram("#\n", cps);
6013 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6014 bp = &boards[moveNum][i][left];
6015 for (j = left; j < right; j++, bp++) {
6016 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6017 if ((int) *bp < (int) BlackPawn) {
6018 if(j == BOARD_RGHT+1)
6019 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6020 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6021 if(message[0] == '+' || message[0] == '~') {
6022 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6023 PieceToChar((ChessSquare)(DEMOTED *bp)),
6026 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6027 message[1] = BOARD_RGHT - 1 - j + '1';
6028 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6030 SendToProgram(message, cps);
6035 SendToProgram("c\n", cps);
6036 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6037 bp = &boards[moveNum][i][left];
6038 for (j = left; j < right; j++, bp++) {
6039 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6040 if (((int) *bp != (int) EmptySquare)
6041 && ((int) *bp >= (int) BlackPawn)) {
6042 if(j == BOARD_LEFT-2)
6043 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6044 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6046 if(message[0] == '+' || message[0] == '~') {
6047 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6048 PieceToChar((ChessSquare)(DEMOTED *bp)),
6051 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6052 message[1] = BOARD_RGHT - 1 - j + '1';
6053 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6055 SendToProgram(message, cps);
6060 SendToProgram(".\n", cps);
6062 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6066 DefaultPromoChoice (int white)
6069 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6070 result = WhiteFerz; // no choice
6071 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6072 result= WhiteKing; // in Suicide Q is the last thing we want
6073 else if(gameInfo.variant == VariantSpartan)
6074 result = white ? WhiteQueen : WhiteAngel;
6075 else result = WhiteQueen;
6076 if(!white) result = WHITE_TO_BLACK result;
6080 static int autoQueen; // [HGM] oneclick
6083 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6085 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6086 /* [HGM] add Shogi promotions */
6087 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6092 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6093 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6095 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6096 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6099 piece = boards[currentMove][fromY][fromX];
6100 if(gameInfo.variant == VariantShogi) {
6101 promotionZoneSize = BOARD_HEIGHT/3;
6102 highestPromotingPiece = (int)WhiteFerz;
6103 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6104 promotionZoneSize = 3;
6107 // Treat Lance as Pawn when it is not representing Amazon
6108 if(gameInfo.variant != VariantSuper) {
6109 if(piece == WhiteLance) piece = WhitePawn; else
6110 if(piece == BlackLance) piece = BlackPawn;
6113 // next weed out all moves that do not touch the promotion zone at all
6114 if((int)piece >= BlackPawn) {
6115 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6117 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6119 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6120 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6123 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6125 // weed out mandatory Shogi promotions
6126 if(gameInfo.variant == VariantShogi) {
6127 if(piece >= BlackPawn) {
6128 if(toY == 0 && piece == BlackPawn ||
6129 toY == 0 && piece == BlackQueen ||
6130 toY <= 1 && piece == BlackKnight) {
6135 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6136 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6137 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6144 // weed out obviously illegal Pawn moves
6145 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6146 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6147 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6148 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6149 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6150 // note we are not allowed to test for valid (non-)capture, due to premove
6153 // we either have a choice what to promote to, or (in Shogi) whether to promote
6154 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6155 *promoChoice = PieceToChar(BlackFerz); // no choice
6158 // no sense asking what we must promote to if it is going to explode...
6159 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6160 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6163 // give caller the default choice even if we will not make it
6164 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6165 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6166 if( sweepSelect && gameInfo.variant != VariantGreat
6167 && gameInfo.variant != VariantGrand
6168 && gameInfo.variant != VariantSuper) return FALSE;
6169 if(autoQueen) return FALSE; // predetermined
6171 // suppress promotion popup on illegal moves that are not premoves
6172 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6173 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6174 if(appData.testLegality && !premove) {
6175 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6176 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6177 if(moveType != WhitePromotion && moveType != BlackPromotion)
6185 InPalace (int row, int column)
6186 { /* [HGM] for Xiangqi */
6187 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6188 column < (BOARD_WIDTH + 4)/2 &&
6189 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6194 PieceForSquare (int x, int y)
6196 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6199 return boards[currentMove][y][x];
6203 OKToStartUserMove (int x, int y)
6205 ChessSquare from_piece;
6208 if (matchMode) return FALSE;
6209 if (gameMode == EditPosition) return TRUE;
6211 if (x >= 0 && y >= 0)
6212 from_piece = boards[currentMove][y][x];
6214 from_piece = EmptySquare;
6216 if (from_piece == EmptySquare) return FALSE;
6218 white_piece = (int)from_piece >= (int)WhitePawn &&
6219 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6223 case TwoMachinesPlay:
6231 case MachinePlaysWhite:
6232 case IcsPlayingBlack:
6233 if (appData.zippyPlay) return FALSE;
6235 DisplayMoveError(_("You are playing Black"));
6240 case MachinePlaysBlack:
6241 case IcsPlayingWhite:
6242 if (appData.zippyPlay) return FALSE;
6244 DisplayMoveError(_("You are playing White"));
6249 case PlayFromGameFile:
6250 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6252 if (!white_piece && WhiteOnMove(currentMove)) {
6253 DisplayMoveError(_("It is White's turn"));
6256 if (white_piece && !WhiteOnMove(currentMove)) {
6257 DisplayMoveError(_("It is Black's turn"));
6260 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6261 /* Editing correspondence game history */
6262 /* Could disallow this or prompt for confirmation */
6267 case BeginningOfGame:
6268 if (appData.icsActive) return FALSE;
6269 if (!appData.noChessProgram) {
6271 DisplayMoveError(_("You are playing White"));
6278 if (!white_piece && WhiteOnMove(currentMove)) {
6279 DisplayMoveError(_("It is White's turn"));
6282 if (white_piece && !WhiteOnMove(currentMove)) {
6283 DisplayMoveError(_("It is Black's turn"));
6292 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6293 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6294 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6295 && gameMode != AnalyzeFile && gameMode != Training) {
6296 DisplayMoveError(_("Displayed position is not current"));
6303 OnlyMove (int *x, int *y, Boolean captures)
6305 DisambiguateClosure cl;
6306 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6308 case MachinePlaysBlack:
6309 case IcsPlayingWhite:
6310 case BeginningOfGame:
6311 if(!WhiteOnMove(currentMove)) return FALSE;
6313 case MachinePlaysWhite:
6314 case IcsPlayingBlack:
6315 if(WhiteOnMove(currentMove)) return FALSE;
6322 cl.pieceIn = EmptySquare;
6327 cl.promoCharIn = NULLCHAR;
6328 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6329 if( cl.kind == NormalMove ||
6330 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6331 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6332 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6339 if(cl.kind != ImpossibleMove) return FALSE;
6340 cl.pieceIn = EmptySquare;
6345 cl.promoCharIn = NULLCHAR;
6346 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6347 if( cl.kind == NormalMove ||
6348 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6349 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6350 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6361 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6362 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6363 int lastLoadGameUseList = FALSE;
6364 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6365 ChessMove lastLoadGameStart = EndOfFile;
6368 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6371 ChessSquare pdown, pup;
6373 /* Check if the user is playing in turn. This is complicated because we
6374 let the user "pick up" a piece before it is his turn. So the piece he
6375 tried to pick up may have been captured by the time he puts it down!
6376 Therefore we use the color the user is supposed to be playing in this
6377 test, not the color of the piece that is currently on the starting
6378 square---except in EditGame mode, where the user is playing both
6379 sides; fortunately there the capture race can't happen. (It can
6380 now happen in IcsExamining mode, but that's just too bad. The user
6381 will get a somewhat confusing message in that case.)
6386 case TwoMachinesPlay:
6390 /* We switched into a game mode where moves are not accepted,
6391 perhaps while the mouse button was down. */
6394 case MachinePlaysWhite:
6395 /* User is moving for Black */
6396 if (WhiteOnMove(currentMove)) {
6397 DisplayMoveError(_("It is White's turn"));
6402 case MachinePlaysBlack:
6403 /* User is moving for White */
6404 if (!WhiteOnMove(currentMove)) {
6405 DisplayMoveError(_("It is Black's turn"));
6410 case PlayFromGameFile:
6411 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6414 case BeginningOfGame:
6417 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6418 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6419 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6420 /* User is moving for Black */
6421 if (WhiteOnMove(currentMove)) {
6422 DisplayMoveError(_("It is White's turn"));
6426 /* User is moving for White */
6427 if (!WhiteOnMove(currentMove)) {
6428 DisplayMoveError(_("It is Black's turn"));
6434 case IcsPlayingBlack:
6435 /* User is moving for Black */
6436 if (WhiteOnMove(currentMove)) {
6437 if (!appData.premove) {
6438 DisplayMoveError(_("It is White's turn"));
6439 } else if (toX >= 0 && toY >= 0) {
6442 premoveFromX = fromX;
6443 premoveFromY = fromY;
6444 premovePromoChar = promoChar;
6446 if (appData.debugMode)
6447 fprintf(debugFP, "Got premove: fromX %d,"
6448 "fromY %d, toX %d, toY %d\n",
6449 fromX, fromY, toX, toY);
6455 case IcsPlayingWhite:
6456 /* User is moving for White */
6457 if (!WhiteOnMove(currentMove)) {
6458 if (!appData.premove) {
6459 DisplayMoveError(_("It is Black's turn"));
6460 } else if (toX >= 0 && toY >= 0) {
6463 premoveFromX = fromX;
6464 premoveFromY = fromY;
6465 premovePromoChar = promoChar;
6467 if (appData.debugMode)
6468 fprintf(debugFP, "Got premove: fromX %d,"
6469 "fromY %d, toX %d, toY %d\n",
6470 fromX, fromY, toX, toY);
6480 /* EditPosition, empty square, or different color piece;
6481 click-click move is possible */
6482 if (toX == -2 || toY == -2) {
6483 boards[0][fromY][fromX] = EmptySquare;
6484 DrawPosition(FALSE, boards[currentMove]);
6486 } else if (toX >= 0 && toY >= 0) {
6487 boards[0][toY][toX] = boards[0][fromY][fromX];
6488 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6489 if(boards[0][fromY][0] != EmptySquare) {
6490 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6491 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6494 if(fromX == BOARD_RGHT+1) {
6495 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6496 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6497 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6500 boards[0][fromY][fromX] = EmptySquare;
6501 DrawPosition(FALSE, boards[currentMove]);
6507 if(toX < 0 || toY < 0) return;
6508 pdown = boards[currentMove][fromY][fromX];
6509 pup = boards[currentMove][toY][toX];
6511 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6512 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6513 if( pup != EmptySquare ) return;
6514 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6515 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6516 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6517 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6518 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6519 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6520 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6524 /* [HGM] always test for legality, to get promotion info */
6525 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6526 fromY, fromX, toY, toX, promoChar);
6528 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6530 /* [HGM] but possibly ignore an IllegalMove result */
6531 if (appData.testLegality) {
6532 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6533 DisplayMoveError(_("Illegal move"));
6538 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6541 /* Common tail of UserMoveEvent and DropMenuEvent */
6543 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6547 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6548 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6549 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6550 if(WhiteOnMove(currentMove)) {
6551 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6553 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6557 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6558 move type in caller when we know the move is a legal promotion */
6559 if(moveType == NormalMove && promoChar)
6560 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6562 /* [HGM] <popupFix> The following if has been moved here from
6563 UserMoveEvent(). Because it seemed to belong here (why not allow
6564 piece drops in training games?), and because it can only be
6565 performed after it is known to what we promote. */
6566 if (gameMode == Training) {
6567 /* compare the move played on the board to the next move in the
6568 * game. If they match, display the move and the opponent's response.
6569 * If they don't match, display an error message.
6573 CopyBoard(testBoard, boards[currentMove]);
6574 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6576 if (CompareBoards(testBoard, boards[currentMove+1])) {
6577 ForwardInner(currentMove+1);
6579 /* Autoplay the opponent's response.
6580 * if appData.animate was TRUE when Training mode was entered,
6581 * the response will be animated.
6583 saveAnimate = appData.animate;
6584 appData.animate = animateTraining;
6585 ForwardInner(currentMove+1);
6586 appData.animate = saveAnimate;
6588 /* check for the end of the game */
6589 if (currentMove >= forwardMostMove) {
6590 gameMode = PlayFromGameFile;
6592 SetTrainingModeOff();
6593 DisplayInformation(_("End of game"));
6596 DisplayError(_("Incorrect move"), 0);
6601 /* Ok, now we know that the move is good, so we can kill
6602 the previous line in Analysis Mode */
6603 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6604 && currentMove < forwardMostMove) {
6605 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6606 else forwardMostMove = currentMove;
6609 /* If we need the chess program but it's dead, restart it */
6610 ResurrectChessProgram();
6612 /* A user move restarts a paused game*/
6616 thinkOutput[0] = NULLCHAR;
6618 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6620 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6621 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6625 if (gameMode == BeginningOfGame) {
6626 if (appData.noChessProgram) {
6627 gameMode = EditGame;
6631 gameMode = MachinePlaysBlack;
6634 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6636 if (first.sendName) {
6637 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6638 SendToProgram(buf, &first);
6645 /* Relay move to ICS or chess engine */
6646 if (appData.icsActive) {
6647 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6648 gameMode == IcsExamining) {
6649 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6650 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6654 // also send plain move, in case ICS does not understand atomic claims
6655 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6659 if (first.sendTime && (gameMode == BeginningOfGame ||
6660 gameMode == MachinePlaysWhite ||
6661 gameMode == MachinePlaysBlack)) {
6662 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6664 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6665 // [HGM] book: if program might be playing, let it use book
6666 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6667 first.maybeThinking = TRUE;
6668 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6669 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6670 SendBoard(&first, currentMove+1);
6671 } else SendMoveToProgram(forwardMostMove-1, &first);
6672 if (currentMove == cmailOldMove + 1) {
6673 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6677 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6681 if(appData.testLegality)
6682 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6688 if (WhiteOnMove(currentMove)) {
6689 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6691 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6695 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6700 case MachinePlaysBlack:
6701 case MachinePlaysWhite:
6702 /* disable certain menu options while machine is thinking */
6703 SetMachineThinkingEnables();
6710 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6711 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6713 if(bookHit) { // [HGM] book: simulate book reply
6714 static char bookMove[MSG_SIZ]; // a bit generous?
6716 programStats.nodes = programStats.depth = programStats.time =
6717 programStats.score = programStats.got_only_move = 0;
6718 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6720 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6721 strcat(bookMove, bookHit);
6722 HandleMachineMove(bookMove, &first);
6728 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6730 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6731 Markers *m = (Markers *) closure;
6732 if(rf == fromY && ff == fromX)
6733 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6734 || kind == WhiteCapturesEnPassant
6735 || kind == BlackCapturesEnPassant);
6736 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6740 MarkTargetSquares (int clear)
6743 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6744 !appData.testLegality || gameMode == EditPosition) return;
6746 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6749 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6750 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6751 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6753 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6756 DrawPosition(TRUE, NULL);
6760 Explode (Board board, int fromX, int fromY, int toX, int toY)
6762 if(gameInfo.variant == VariantAtomic &&
6763 (board[toY][toX] != EmptySquare || // capture?
6764 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6765 board[fromY][fromX] == BlackPawn )
6767 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6773 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6776 CanPromote (ChessSquare piece, int y)
6778 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6779 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6780 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6781 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6782 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6783 gameInfo.variant == VariantMakruk) return FALSE;
6784 return (piece == BlackPawn && y == 1 ||
6785 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6786 piece == BlackLance && y == 1 ||
6787 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6791 LeftClick (ClickType clickType, int xPix, int yPix)
6794 Boolean saveAnimate;
6795 static int second = 0, promotionChoice = 0, clearFlag = 0;
6796 char promoChoice = NULLCHAR;
6799 if(appData.seekGraph && appData.icsActive && loggedOn &&
6800 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6801 SeekGraphClick(clickType, xPix, yPix, 0);
6805 if (clickType == Press) ErrorPopDown();
6807 x = EventToSquare(xPix, BOARD_WIDTH);
6808 y = EventToSquare(yPix, BOARD_HEIGHT);
6809 if (!flipView && y >= 0) {
6810 y = BOARD_HEIGHT - 1 - y;
6812 if (flipView && x >= 0) {
6813 x = BOARD_WIDTH - 1 - x;
6816 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6817 defaultPromoChoice = promoSweep;
6818 promoSweep = EmptySquare; // terminate sweep
6819 promoDefaultAltered = TRUE;
6820 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6823 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6824 if(clickType == Release) return; // ignore upclick of click-click destination
6825 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6826 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6827 if(gameInfo.holdingsWidth &&
6828 (WhiteOnMove(currentMove)
6829 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6830 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6831 // click in right holdings, for determining promotion piece
6832 ChessSquare p = boards[currentMove][y][x];
6833 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6834 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6835 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6836 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6841 DrawPosition(FALSE, boards[currentMove]);
6845 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6846 if(clickType == Press
6847 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6848 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6849 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6852 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6853 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6855 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6856 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6857 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6858 defaultPromoChoice = DefaultPromoChoice(side);
6861 autoQueen = appData.alwaysPromoteToQueen;
6865 gatingPiece = EmptySquare;
6866 if (clickType != Press) {
6867 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6868 DragPieceEnd(xPix, yPix); dragging = 0;
6869 DrawPosition(FALSE, NULL);
6873 fromX = x; fromY = y; toX = toY = -1;
6874 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6875 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6876 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6878 if (OKToStartUserMove(fromX, fromY)) {
6880 MarkTargetSquares(0);
6881 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6882 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6883 promoSweep = defaultPromoChoice;
6884 selectFlag = 0; lastX = xPix; lastY = yPix;
6885 Sweep(0); // Pawn that is going to promote: preview promotion piece
6886 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6888 if (appData.highlightDragging) {
6889 SetHighlights(fromX, fromY, -1, -1);
6891 } else fromX = fromY = -1;
6897 if (clickType == Press && gameMode != EditPosition) {
6902 // ignore off-board to clicks
6903 if(y < 0 || x < 0) return;
6905 /* Check if clicking again on the same color piece */
6906 fromP = boards[currentMove][fromY][fromX];
6907 toP = boards[currentMove][y][x];
6908 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6909 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6910 WhitePawn <= toP && toP <= WhiteKing &&
6911 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6912 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6913 (BlackPawn <= fromP && fromP <= BlackKing &&
6914 BlackPawn <= toP && toP <= BlackKing &&
6915 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6916 !(fromP == BlackKing && toP == BlackRook && frc))) {
6917 /* Clicked again on same color piece -- changed his mind */
6918 second = (x == fromX && y == fromY);
6919 promoDefaultAltered = FALSE;
6920 MarkTargetSquares(1);
6921 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6922 if (appData.highlightDragging) {
6923 SetHighlights(x, y, -1, -1);
6927 if (OKToStartUserMove(x, y)) {
6928 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6929 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6930 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6931 gatingPiece = boards[currentMove][fromY][fromX];
6932 else gatingPiece = EmptySquare;
6934 fromY = y; dragging = 1;
6935 MarkTargetSquares(0);
6936 DragPieceBegin(xPix, yPix, FALSE);
6937 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6938 promoSweep = defaultPromoChoice;
6939 selectFlag = 0; lastX = xPix; lastY = yPix;
6940 Sweep(0); // Pawn that is going to promote: preview promotion piece
6944 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6947 // ignore clicks on holdings
6948 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6951 if (clickType == Release && x == fromX && y == fromY) {
6952 DragPieceEnd(xPix, yPix); dragging = 0;
6954 // a deferred attempt to click-click move an empty square on top of a piece
6955 boards[currentMove][y][x] = EmptySquare;
6957 DrawPosition(FALSE, boards[currentMove]);
6958 fromX = fromY = -1; clearFlag = 0;
6961 if (appData.animateDragging) {
6962 /* Undo animation damage if any */
6963 DrawPosition(FALSE, NULL);
6966 /* Second up/down in same square; just abort move */
6969 gatingPiece = EmptySquare;
6972 ClearPremoveHighlights();
6974 /* First upclick in same square; start click-click mode */
6975 SetHighlights(x, y, -1, -1);
6982 /* we now have a different from- and (possibly off-board) to-square */
6983 /* Completed move */
6986 saveAnimate = appData.animate;
6987 MarkTargetSquares(1);
6988 if (clickType == Press) {
6989 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6990 // must be Edit Position mode with empty-square selected
6991 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6992 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6995 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6996 ChessSquare piece = boards[currentMove][fromY][fromX];
6997 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6998 promoSweep = defaultPromoChoice;
6999 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7000 selectFlag = 0; lastX = xPix; lastY = yPix;
7001 Sweep(0); // Pawn that is going to promote: preview promotion piece
7002 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7003 DrawPosition(FALSE, boards[currentMove]);
7006 /* Finish clickclick move */
7007 if (appData.animate || appData.highlightLastMove) {
7008 SetHighlights(fromX, fromY, toX, toY);
7013 /* Finish drag move */
7014 if (appData.highlightLastMove) {
7015 SetHighlights(fromX, fromY, toX, toY);
7019 DragPieceEnd(xPix, yPix); dragging = 0;
7020 /* Don't animate move and drag both */
7021 appData.animate = FALSE;
7024 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7025 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7026 ChessSquare piece = boards[currentMove][fromY][fromX];
7027 if(gameMode == EditPosition && piece != EmptySquare &&
7028 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7031 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7032 n = PieceToNumber(piece - (int)BlackPawn);
7033 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7034 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7035 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7037 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7038 n = PieceToNumber(piece);
7039 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7040 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7041 boards[currentMove][n][BOARD_WIDTH-2]++;
7043 boards[currentMove][fromY][fromX] = EmptySquare;
7047 DrawPosition(TRUE, boards[currentMove]);
7051 // off-board moves should not be highlighted
7052 if(x < 0 || y < 0) ClearHighlights();
7054 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7056 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7057 SetHighlights(fromX, fromY, toX, toY);
7058 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7059 // [HGM] super: promotion to captured piece selected from holdings
7060 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7061 promotionChoice = TRUE;
7062 // kludge follows to temporarily execute move on display, without promoting yet
7063 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7064 boards[currentMove][toY][toX] = p;
7065 DrawPosition(FALSE, boards[currentMove]);
7066 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7067 boards[currentMove][toY][toX] = q;
7068 DisplayMessage("Click in holdings to choose piece", "");
7073 int oldMove = currentMove;
7074 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7075 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7076 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7077 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7078 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7079 DrawPosition(TRUE, boards[currentMove]);
7082 appData.animate = saveAnimate;
7083 if (appData.animate || appData.animateDragging) {
7084 /* Undo animation damage if needed */
7085 DrawPosition(FALSE, NULL);
7090 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7091 { // front-end-free part taken out of PieceMenuPopup
7092 int whichMenu; int xSqr, ySqr;
7094 if(seekGraphUp) { // [HGM] seekgraph
7095 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7096 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7100 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7101 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7102 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7103 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7104 if(action == Press) {
7105 originalFlip = flipView;
7106 flipView = !flipView; // temporarily flip board to see game from partners perspective
7107 DrawPosition(TRUE, partnerBoard);
7108 DisplayMessage(partnerStatus, "");
7110 } else if(action == Release) {
7111 flipView = originalFlip;
7112 DrawPosition(TRUE, boards[currentMove]);
7118 xSqr = EventToSquare(x, BOARD_WIDTH);
7119 ySqr = EventToSquare(y, BOARD_HEIGHT);
7120 if (action == Release) {
7121 if(pieceSweep != EmptySquare) {
7122 EditPositionMenuEvent(pieceSweep, toX, toY);
7123 pieceSweep = EmptySquare;
7124 } else UnLoadPV(); // [HGM] pv
7126 if (action != Press) return -2; // return code to be ignored
7129 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7131 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7132 if (xSqr < 0 || ySqr < 0) return -1;
7133 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7134 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7135 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7136 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7140 if(!appData.icsEngineAnalyze) return -1;
7141 case IcsPlayingWhite:
7142 case IcsPlayingBlack:
7143 if(!appData.zippyPlay) goto noZip;
7146 case MachinePlaysWhite:
7147 case MachinePlaysBlack:
7148 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7149 if (!appData.dropMenu) {
7151 return 2; // flag front-end to grab mouse events
7153 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7154 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7157 if (xSqr < 0 || ySqr < 0) return -1;
7158 if (!appData.dropMenu || appData.testLegality &&
7159 gameInfo.variant != VariantBughouse &&
7160 gameInfo.variant != VariantCrazyhouse) return -1;
7161 whichMenu = 1; // drop menu
7167 if (((*fromX = xSqr) < 0) ||
7168 ((*fromY = ySqr) < 0)) {
7169 *fromX = *fromY = -1;
7173 *fromX = BOARD_WIDTH - 1 - *fromX;
7175 *fromY = BOARD_HEIGHT - 1 - *fromY;
7181 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7183 // char * hint = lastHint;
7184 FrontEndProgramStats stats;
7186 stats.which = cps == &first ? 0 : 1;
7187 stats.depth = cpstats->depth;
7188 stats.nodes = cpstats->nodes;
7189 stats.score = cpstats->score;
7190 stats.time = cpstats->time;
7191 stats.pv = cpstats->movelist;
7192 stats.hint = lastHint;
7193 stats.an_move_index = 0;
7194 stats.an_move_count = 0;
7196 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7197 stats.hint = cpstats->move_name;
7198 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7199 stats.an_move_count = cpstats->nr_moves;
7202 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
7204 SetProgramStats( &stats );
7208 ClearEngineOutputPane (int which)
7210 static FrontEndProgramStats dummyStats;
7211 dummyStats.which = which;
7212 dummyStats.pv = "#";
7213 SetProgramStats( &dummyStats );
7216 #define MAXPLAYERS 500
7219 TourneyStandings (int display)
7221 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7222 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7223 char result, *p, *names[MAXPLAYERS];
7225 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7226 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7227 names[0] = p = strdup(appData.participants);
7228 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7230 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7232 while(result = appData.results[nr]) {
7233 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7234 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7235 wScore = bScore = 0;
7237 case '+': wScore = 2; break;
7238 case '-': bScore = 2; break;
7239 case '=': wScore = bScore = 1; break;
7241 case '*': return strdup("busy"); // tourney not finished
7249 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7250 for(w=0; w<nPlayers; w++) {
7252 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7253 ranking[w] = b; points[w] = bScore; score[b] = -2;
7255 p = malloc(nPlayers*34+1);
7256 for(w=0; w<nPlayers && w<display; w++)
7257 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7263 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7264 { // count all piece types
7266 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7267 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7268 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7271 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7272 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7273 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7274 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7275 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7276 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7281 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7283 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7284 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7286 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7287 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7288 if(myPawns == 2 && nMine == 3) // KPP
7289 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7290 if(myPawns == 1 && nMine == 2) // KP
7291 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7292 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7293 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7294 if(myPawns) return FALSE;
7295 if(pCnt[WhiteRook+side])
7296 return pCnt[BlackRook-side] ||
7297 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7298 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7299 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7300 if(pCnt[WhiteCannon+side]) {
7301 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7302 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7304 if(pCnt[WhiteKnight+side])
7305 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7310 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7312 VariantClass v = gameInfo.variant;
7314 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7315 if(v == VariantShatranj) return TRUE; // always winnable through baring
7316 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7317 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7319 if(v == VariantXiangqi) {
7320 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7322 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7323 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7324 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7325 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7326 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7327 if(stale) // we have at least one last-rank P plus perhaps C
7328 return majors // KPKX
7329 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7331 return pCnt[WhiteFerz+side] // KCAK
7332 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7333 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7334 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7336 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7337 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7339 if(nMine == 1) return FALSE; // bare King
7340 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
7341 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7342 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7343 // by now we have King + 1 piece (or multiple Bishops on the same color)
7344 if(pCnt[WhiteKnight+side])
7345 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7346 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7347 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7349 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7350 if(pCnt[WhiteAlfil+side])
7351 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7352 if(pCnt[WhiteWazir+side])
7353 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7360 CompareWithRights (Board b1, Board b2)
7363 if(!CompareBoards(b1, b2)) return FALSE;
7364 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7365 /* compare castling rights */
7366 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7367 rights++; /* King lost rights, while rook still had them */
7368 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7369 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7370 rights++; /* but at least one rook lost them */
7372 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7374 if( b1[CASTLING][5] != NoRights ) {
7375 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7382 Adjudicate (ChessProgramState *cps)
7383 { // [HGM] some adjudications useful with buggy engines
7384 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7385 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7386 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7387 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7388 int k, count = 0; static int bare = 1;
7389 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7390 Boolean canAdjudicate = !appData.icsActive;
7392 // most tests only when we understand the game, i.e. legality-checking on
7393 if( appData.testLegality )
7394 { /* [HGM] Some more adjudications for obstinate engines */
7395 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7396 static int moveCount = 6;
7398 char *reason = NULL;
7400 /* Count what is on board. */
7401 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7403 /* Some material-based adjudications that have to be made before stalemate test */
7404 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7405 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7406 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7407 if(canAdjudicate && appData.checkMates) {
7409 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7410 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7411 "Xboard adjudication: King destroyed", GE_XBOARD );
7416 /* Bare King in Shatranj (loses) or Losers (wins) */
7417 if( nrW == 1 || nrB == 1) {
7418 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7419 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7420 if(canAdjudicate && appData.checkMates) {
7422 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7423 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7424 "Xboard adjudication: Bare king", GE_XBOARD );
7428 if( gameInfo.variant == VariantShatranj && --bare < 0)
7430 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7431 if(canAdjudicate && appData.checkMates) {
7432 /* but only adjudicate if adjudication enabled */
7434 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7435 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7436 "Xboard adjudication: Bare king", GE_XBOARD );
7443 // don't wait for engine to announce game end if we can judge ourselves
7444 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7446 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7447 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7448 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7449 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7452 reason = "Xboard adjudication: 3rd check";
7453 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7463 reason = "Xboard adjudication: Stalemate";
7464 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7465 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7466 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7467 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7468 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7469 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7470 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7471 EP_CHECKMATE : EP_WINS);
7472 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7473 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7477 reason = "Xboard adjudication: Checkmate";
7478 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7482 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7484 result = GameIsDrawn; break;
7486 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7488 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7492 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7494 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7495 GameEnds( result, reason, GE_XBOARD );
7499 /* Next absolutely insufficient mating material. */
7500 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7501 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7502 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7504 /* always flag draws, for judging claims */
7505 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7507 if(canAdjudicate && appData.materialDraws) {
7508 /* but only adjudicate them if adjudication enabled */
7509 if(engineOpponent) {
7510 SendToProgram("force\n", engineOpponent); // suppress reply
7511 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7513 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7518 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7519 if(gameInfo.variant == VariantXiangqi ?
7520 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7522 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7523 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7524 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7525 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7527 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7528 { /* if the first 3 moves do not show a tactical win, declare draw */
7529 if(engineOpponent) {
7530 SendToProgram("force\n", engineOpponent); // suppress reply
7531 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7533 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7536 } else moveCount = 6;
7538 if (appData.debugMode) { int i;
7539 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7540 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7541 appData.drawRepeats);
7542 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7543 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7547 // Repetition draws and 50-move rule can be applied independently of legality testing
7549 /* Check for rep-draws */
7551 for(k = forwardMostMove-2;
7552 k>=backwardMostMove && k>=forwardMostMove-100 &&
7553 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7554 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7557 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7558 /* compare castling rights */
7559 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7560 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7561 rights++; /* King lost rights, while rook still had them */
7562 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7563 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7564 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7565 rights++; /* but at least one rook lost them */
7567 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7568 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7570 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7571 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7572 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7575 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7576 && appData.drawRepeats > 1) {
7577 /* adjudicate after user-specified nr of repeats */
7578 int result = GameIsDrawn;
7579 char *details = "XBoard adjudication: repetition draw";
7580 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7581 // [HGM] xiangqi: check for forbidden perpetuals
7582 int m, ourPerpetual = 1, hisPerpetual = 1;
7583 for(m=forwardMostMove; m>k; m-=2) {
7584 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7585 ourPerpetual = 0; // the current mover did not always check
7586 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7587 hisPerpetual = 0; // the opponent did not always check
7589 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7590 ourPerpetual, hisPerpetual);
7591 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7592 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7593 details = "Xboard adjudication: perpetual checking";
7595 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7596 break; // (or we would have caught him before). Abort repetition-checking loop.
7598 // Now check for perpetual chases
7599 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7600 hisPerpetual = PerpetualChase(k, forwardMostMove);
7601 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7602 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7603 static char resdet[MSG_SIZ];
7604 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7606 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7608 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7609 break; // Abort repetition-checking loop.
7611 // if neither of us is checking or chasing all the time, or both are, it is draw
7613 if(engineOpponent) {
7614 SendToProgram("force\n", engineOpponent); // suppress reply
7615 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7617 GameEnds( result, details, GE_XBOARD );
7620 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7621 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7625 /* Now we test for 50-move draws. Determine ply count */
7626 count = forwardMostMove;
7627 /* look for last irreversble move */
7628 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7630 /* if we hit starting position, add initial plies */
7631 if( count == backwardMostMove )
7632 count -= initialRulePlies;
7633 count = forwardMostMove - count;
7634 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7635 // adjust reversible move counter for checks in Xiangqi
7636 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7637 if(i < backwardMostMove) i = backwardMostMove;
7638 while(i <= forwardMostMove) {
7639 lastCheck = inCheck; // check evasion does not count
7640 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7641 if(inCheck || lastCheck) count--; // check does not count
7646 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7647 /* this is used to judge if draw claims are legal */
7648 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7649 if(engineOpponent) {
7650 SendToProgram("force\n", engineOpponent); // suppress reply
7651 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7653 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7657 /* if draw offer is pending, treat it as a draw claim
7658 * when draw condition present, to allow engines a way to
7659 * claim draws before making their move to avoid a race
7660 * condition occurring after their move
7662 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7664 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7665 p = "Draw claim: 50-move rule";
7666 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7667 p = "Draw claim: 3-fold repetition";
7668 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7669 p = "Draw claim: insufficient mating material";
7670 if( p != NULL && canAdjudicate) {
7671 if(engineOpponent) {
7672 SendToProgram("force\n", engineOpponent); // suppress reply
7673 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7675 GameEnds( GameIsDrawn, p, GE_XBOARD );
7680 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7681 if(engineOpponent) {
7682 SendToProgram("force\n", engineOpponent); // suppress reply
7683 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7685 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7692 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7693 { // [HGM] book: this routine intercepts moves to simulate book replies
7694 char *bookHit = NULL;
7696 //first determine if the incoming move brings opponent into his book
7697 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7698 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7699 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7700 if(bookHit != NULL && !cps->bookSuspend) {
7701 // make sure opponent is not going to reply after receiving move to book position
7702 SendToProgram("force\n", cps);
7703 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7705 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7706 // now arrange restart after book miss
7708 // after a book hit we never send 'go', and the code after the call to this routine
7709 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7710 char buf[MSG_SIZ], *move = bookHit;
7712 int fromX, fromY, toX, toY;
7716 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7717 &fromX, &fromY, &toX, &toY, &promoChar)) {
7718 (void) CoordsToAlgebraic(boards[forwardMostMove],
7719 PosFlags(forwardMostMove),
7720 fromY, fromX, toY, toX, promoChar, move);
7722 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7726 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7727 SendToProgram(buf, cps);
7728 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7729 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7730 SendToProgram("go\n", cps);
7731 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7732 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7733 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7734 SendToProgram("go\n", cps);
7735 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7737 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7741 ChessProgramState *savedState;
7743 DeferredBookMove (void)
7745 if(savedState->lastPing != savedState->lastPong)
7746 ScheduleDelayedEvent(DeferredBookMove, 10);
7748 HandleMachineMove(savedMessage, savedState);
7751 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7754 HandleMachineMove (char *message, ChessProgramState *cps)
7756 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7757 char realname[MSG_SIZ];
7758 int fromX, fromY, toX, toY;
7765 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7766 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7767 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7768 DisplayError(_("Invalid pairing from pairing engine"), 0);
7771 pairingReceived = 1;
7773 return; // Skim the pairing messages here.
7778 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7780 * Kludge to ignore BEL characters
7782 while (*message == '\007') message++;
7785 * [HGM] engine debug message: ignore lines starting with '#' character
7787 if(cps->debug && *message == '#') return;
7790 * Look for book output
7792 if (cps == &first && bookRequested) {
7793 if (message[0] == '\t' || message[0] == ' ') {
7794 /* Part of the book output is here; append it */
7795 strcat(bookOutput, message);
7796 strcat(bookOutput, " \n");
7798 } else if (bookOutput[0] != NULLCHAR) {
7799 /* All of book output has arrived; display it */
7800 char *p = bookOutput;
7801 while (*p != NULLCHAR) {
7802 if (*p == '\t') *p = ' ';
7805 DisplayInformation(bookOutput);
7806 bookRequested = FALSE;
7807 /* Fall through to parse the current output */
7812 * Look for machine move.
7814 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7815 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7817 /* This method is only useful on engines that support ping */
7818 if (cps->lastPing != cps->lastPong) {
7819 if (gameMode == BeginningOfGame) {
7820 /* Extra move from before last new; ignore */
7821 if (appData.debugMode) {
7822 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7825 if (appData.debugMode) {
7826 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7827 cps->which, gameMode);
7830 SendToProgram("undo\n", cps);
7836 case BeginningOfGame:
7837 /* Extra move from before last reset; ignore */
7838 if (appData.debugMode) {
7839 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7846 /* Extra move after we tried to stop. The mode test is
7847 not a reliable way of detecting this problem, but it's
7848 the best we can do on engines that don't support ping.
7850 if (appData.debugMode) {
7851 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7852 cps->which, gameMode);
7854 SendToProgram("undo\n", cps);
7857 case MachinePlaysWhite:
7858 case IcsPlayingWhite:
7859 machineWhite = TRUE;
7862 case MachinePlaysBlack:
7863 case IcsPlayingBlack:
7864 machineWhite = FALSE;
7867 case TwoMachinesPlay:
7868 machineWhite = (cps->twoMachinesColor[0] == 'w');
7871 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7872 if (appData.debugMode) {
7874 "Ignoring move out of turn by %s, gameMode %d"
7875 ", forwardMost %d\n",
7876 cps->which, gameMode, forwardMostMove);
7881 if (appData.debugMode) { int f = forwardMostMove;
7882 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7883 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7884 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7886 if(cps->alphaRank) AlphaRank(machineMove, 4);
7887 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7888 &fromX, &fromY, &toX, &toY, &promoChar)) {
7889 /* Machine move could not be parsed; ignore it. */
7890 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7891 machineMove, _(cps->which));
7892 DisplayError(buf1, 0);
7893 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7894 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7895 if (gameMode == TwoMachinesPlay) {
7896 GameEnds(machineWhite ? BlackWins : WhiteWins,
7902 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7903 /* So we have to redo legality test with true e.p. status here, */
7904 /* to make sure an illegal e.p. capture does not slip through, */
7905 /* to cause a forfeit on a justified illegal-move complaint */
7906 /* of the opponent. */
7907 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7909 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7910 fromY, fromX, toY, toX, promoChar);
7911 if (appData.debugMode) {
7913 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7914 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7915 fprintf(debugFP, "castling rights\n");
7917 if(moveType == IllegalMove) {
7918 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7919 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7920 GameEnds(machineWhite ? BlackWins : WhiteWins,
7923 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7924 /* [HGM] Kludge to handle engines that send FRC-style castling
7925 when they shouldn't (like TSCP-Gothic) */
7927 case WhiteASideCastleFR:
7928 case BlackASideCastleFR:
7930 currentMoveString[2]++;
7932 case WhiteHSideCastleFR:
7933 case BlackHSideCastleFR:
7935 currentMoveString[2]--;
7937 default: ; // nothing to do, but suppresses warning of pedantic compilers
7940 hintRequested = FALSE;
7941 lastHint[0] = NULLCHAR;
7942 bookRequested = FALSE;
7943 /* Program may be pondering now */
7944 cps->maybeThinking = TRUE;
7945 if (cps->sendTime == 2) cps->sendTime = 1;
7946 if (cps->offeredDraw) cps->offeredDraw--;
7948 /* [AS] Save move info*/
7949 pvInfoList[ forwardMostMove ].score = programStats.score;
7950 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7951 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7953 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7955 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7956 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7959 while( count < adjudicateLossPlies ) {
7960 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7963 score = -score; /* Flip score for winning side */
7966 if( score > adjudicateLossThreshold ) {
7973 if( count >= adjudicateLossPlies ) {
7974 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7976 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7977 "Xboard adjudication",
7984 if(Adjudicate(cps)) {
7985 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7986 return; // [HGM] adjudicate: for all automatic game ends
7990 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7992 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7993 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7995 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7997 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7999 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8000 char buf[3*MSG_SIZ];
8002 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8003 programStats.score / 100.,
8005 programStats.time / 100.,
8006 (unsigned int)programStats.nodes,
8007 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8008 programStats.movelist);
8010 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8015 /* [AS] Clear stats for next move */
8016 ClearProgramStats();
8017 thinkOutput[0] = NULLCHAR;
8018 hiddenThinkOutputState = 0;
8021 if (gameMode == TwoMachinesPlay) {
8022 /* [HGM] relaying draw offers moved to after reception of move */
8023 /* and interpreting offer as claim if it brings draw condition */
8024 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8025 SendToProgram("draw\n", cps->other);
8027 if (cps->other->sendTime) {
8028 SendTimeRemaining(cps->other,
8029 cps->other->twoMachinesColor[0] == 'w');
8031 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8032 if (firstMove && !bookHit) {
8034 if (cps->other->useColors) {
8035 SendToProgram(cps->other->twoMachinesColor, cps->other);
8037 SendToProgram("go\n", cps->other);
8039 cps->other->maybeThinking = TRUE;
8042 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8044 if (!pausing && appData.ringBellAfterMoves) {
8049 * Reenable menu items that were disabled while
8050 * machine was thinking
8052 if (gameMode != TwoMachinesPlay)
8053 SetUserThinkingEnables();
8055 // [HGM] book: after book hit opponent has received move and is now in force mode
8056 // force the book reply into it, and then fake that it outputted this move by jumping
8057 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8059 static char bookMove[MSG_SIZ]; // a bit generous?
8061 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8062 strcat(bookMove, bookHit);
8065 programStats.nodes = programStats.depth = programStats.time =
8066 programStats.score = programStats.got_only_move = 0;
8067 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8069 if(cps->lastPing != cps->lastPong) {
8070 savedMessage = message; // args for deferred call
8072 ScheduleDelayedEvent(DeferredBookMove, 10);
8081 /* Set special modes for chess engines. Later something general
8082 * could be added here; for now there is just one kludge feature,
8083 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8084 * when "xboard" is given as an interactive command.
8086 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8087 cps->useSigint = FALSE;
8088 cps->useSigterm = FALSE;
8090 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8091 ParseFeatures(message+8, cps);
8092 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8095 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8096 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8097 int dummy, s=6; char buf[MSG_SIZ];
8098 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8099 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8100 if(startedFromSetupPosition) return;
8101 ParseFEN(boards[0], &dummy, message+s);
8102 DrawPosition(TRUE, boards[0]);
8103 startedFromSetupPosition = TRUE;
8106 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8107 * want this, I was asked to put it in, and obliged.
8109 if (!strncmp(message, "setboard ", 9)) {
8110 Board initial_position;
8112 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8114 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8115 DisplayError(_("Bad FEN received from engine"), 0);
8119 CopyBoard(boards[0], initial_position);
8120 initialRulePlies = FENrulePlies;
8121 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8122 else gameMode = MachinePlaysBlack;
8123 DrawPosition(FALSE, boards[currentMove]);
8129 * Look for communication commands
8131 if (!strncmp(message, "telluser ", 9)) {
8132 if(message[9] == '\\' && message[10] == '\\')
8133 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8135 DisplayNote(message + 9);
8138 if (!strncmp(message, "tellusererror ", 14)) {
8140 if(message[14] == '\\' && message[15] == '\\')
8141 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8143 DisplayError(message + 14, 0);
8146 if (!strncmp(message, "tellopponent ", 13)) {
8147 if (appData.icsActive) {
8149 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8153 DisplayNote(message + 13);
8157 if (!strncmp(message, "tellothers ", 11)) {
8158 if (appData.icsActive) {
8160 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8166 if (!strncmp(message, "tellall ", 8)) {
8167 if (appData.icsActive) {
8169 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8173 DisplayNote(message + 8);
8177 if (strncmp(message, "warning", 7) == 0) {
8178 /* Undocumented feature, use tellusererror in new code */
8179 DisplayError(message, 0);
8182 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8183 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8184 strcat(realname, " query");
8185 AskQuestion(realname, buf2, buf1, cps->pr);
8188 /* Commands from the engine directly to ICS. We don't allow these to be
8189 * sent until we are logged on. Crafty kibitzes have been known to
8190 * interfere with the login process.
8193 if (!strncmp(message, "tellics ", 8)) {
8194 SendToICS(message + 8);
8198 if (!strncmp(message, "tellicsnoalias ", 15)) {
8199 SendToICS(ics_prefix);
8200 SendToICS(message + 15);
8204 /* The following are for backward compatibility only */
8205 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8206 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8207 SendToICS(ics_prefix);
8213 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8217 * If the move is illegal, cancel it and redraw the board.
8218 * Also deal with other error cases. Matching is rather loose
8219 * here to accommodate engines written before the spec.
8221 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8222 strncmp(message, "Error", 5) == 0) {
8223 if (StrStr(message, "name") ||
8224 StrStr(message, "rating") || StrStr(message, "?") ||
8225 StrStr(message, "result") || StrStr(message, "board") ||
8226 StrStr(message, "bk") || StrStr(message, "computer") ||
8227 StrStr(message, "variant") || StrStr(message, "hint") ||
8228 StrStr(message, "random") || StrStr(message, "depth") ||
8229 StrStr(message, "accepted")) {
8232 if (StrStr(message, "protover")) {
8233 /* Program is responding to input, so it's apparently done
8234 initializing, and this error message indicates it is
8235 protocol version 1. So we don't need to wait any longer
8236 for it to initialize and send feature commands. */
8237 FeatureDone(cps, 1);
8238 cps->protocolVersion = 1;
8241 cps->maybeThinking = FALSE;
8243 if (StrStr(message, "draw")) {
8244 /* Program doesn't have "draw" command */
8245 cps->sendDrawOffers = 0;
8248 if (cps->sendTime != 1 &&
8249 (StrStr(message, "time") || StrStr(message, "otim"))) {
8250 /* Program apparently doesn't have "time" or "otim" command */
8254 if (StrStr(message, "analyze")) {
8255 cps->analysisSupport = FALSE;
8256 cps->analyzing = FALSE;
8257 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8258 EditGameEvent(); // [HGM] try to preserve loaded game
8259 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8260 DisplayError(buf2, 0);
8263 if (StrStr(message, "(no matching move)st")) {
8264 /* Special kludge for GNU Chess 4 only */
8265 cps->stKludge = TRUE;
8266 SendTimeControl(cps, movesPerSession, timeControl,
8267 timeIncrement, appData.searchDepth,
8271 if (StrStr(message, "(no matching move)sd")) {
8272 /* Special kludge for GNU Chess 4 only */
8273 cps->sdKludge = TRUE;
8274 SendTimeControl(cps, movesPerSession, timeControl,
8275 timeIncrement, appData.searchDepth,
8279 if (!StrStr(message, "llegal")) {
8282 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8283 gameMode == IcsIdle) return;
8284 if (forwardMostMove <= backwardMostMove) return;
8285 if (pausing) PauseEvent();
8286 if(appData.forceIllegal) {
8287 // [HGM] illegal: machine refused move; force position after move into it
8288 SendToProgram("force\n", cps);
8289 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8290 // we have a real problem now, as SendBoard will use the a2a3 kludge
8291 // when black is to move, while there might be nothing on a2 or black
8292 // might already have the move. So send the board as if white has the move.
8293 // But first we must change the stm of the engine, as it refused the last move
8294 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8295 if(WhiteOnMove(forwardMostMove)) {
8296 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8297 SendBoard(cps, forwardMostMove); // kludgeless board
8299 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8300 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8301 SendBoard(cps, forwardMostMove+1); // kludgeless board
8303 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8304 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8305 gameMode == TwoMachinesPlay)
8306 SendToProgram("go\n", cps);
8309 if (gameMode == PlayFromGameFile) {
8310 /* Stop reading this game file */
8311 gameMode = EditGame;
8314 /* [HGM] illegal-move claim should forfeit game when Xboard */
8315 /* only passes fully legal moves */
8316 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8317 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8318 "False illegal-move claim", GE_XBOARD );
8319 return; // do not take back move we tested as valid
8321 currentMove = forwardMostMove-1;
8322 DisplayMove(currentMove-1); /* before DisplayMoveError */
8323 SwitchClocks(forwardMostMove-1); // [HGM] race
8324 DisplayBothClocks();
8325 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8326 parseList[currentMove], _(cps->which));
8327 DisplayMoveError(buf1);
8328 DrawPosition(FALSE, boards[currentMove]);
8330 SetUserThinkingEnables();
8333 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8334 /* Program has a broken "time" command that
8335 outputs a string not ending in newline.
8341 * If chess program startup fails, exit with an error message.
8342 * Attempts to recover here are futile.
8344 if ((StrStr(message, "unknown host") != NULL)
8345 || (StrStr(message, "No remote directory") != NULL)
8346 || (StrStr(message, "not found") != NULL)
8347 || (StrStr(message, "No such file") != NULL)
8348 || (StrStr(message, "can't alloc") != NULL)
8349 || (StrStr(message, "Permission denied") != NULL)) {
8351 cps->maybeThinking = FALSE;
8352 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8353 _(cps->which), cps->program, cps->host, message);
8354 RemoveInputSource(cps->isr);
8355 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8356 if(cps == &first) appData.noChessProgram = TRUE;
8357 DisplayError(buf1, 0);
8363 * Look for hint output
8365 if (sscanf(message, "Hint: %s", buf1) == 1) {
8366 if (cps == &first && hintRequested) {
8367 hintRequested = FALSE;
8368 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8369 &fromX, &fromY, &toX, &toY, &promoChar)) {
8370 (void) CoordsToAlgebraic(boards[forwardMostMove],
8371 PosFlags(forwardMostMove),
8372 fromY, fromX, toY, toX, promoChar, buf1);
8373 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8374 DisplayInformation(buf2);
8376 /* Hint move could not be parsed!? */
8377 snprintf(buf2, sizeof(buf2),
8378 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8379 buf1, _(cps->which));
8380 DisplayError(buf2, 0);
8383 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8389 * Ignore other messages if game is not in progress
8391 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8392 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8395 * look for win, lose, draw, or draw offer
8397 if (strncmp(message, "1-0", 3) == 0) {
8398 char *p, *q, *r = "";
8399 p = strchr(message, '{');
8407 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8409 } else if (strncmp(message, "0-1", 3) == 0) {
8410 char *p, *q, *r = "";
8411 p = strchr(message, '{');
8419 /* Kludge for Arasan 4.1 bug */
8420 if (strcmp(r, "Black resigns") == 0) {
8421 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8424 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8426 } else if (strncmp(message, "1/2", 3) == 0) {
8427 char *p, *q, *r = "";
8428 p = strchr(message, '{');
8437 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8440 } else if (strncmp(message, "White resign", 12) == 0) {
8441 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8443 } else if (strncmp(message, "Black resign", 12) == 0) {
8444 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8446 } else if (strncmp(message, "White matches", 13) == 0 ||
8447 strncmp(message, "Black matches", 13) == 0 ) {
8448 /* [HGM] ignore GNUShogi noises */
8450 } else if (strncmp(message, "White", 5) == 0 &&
8451 message[5] != '(' &&
8452 StrStr(message, "Black") == NULL) {
8453 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8455 } else if (strncmp(message, "Black", 5) == 0 &&
8456 message[5] != '(') {
8457 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8459 } else if (strcmp(message, "resign") == 0 ||
8460 strcmp(message, "computer resigns") == 0) {
8462 case MachinePlaysBlack:
8463 case IcsPlayingBlack:
8464 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8466 case MachinePlaysWhite:
8467 case IcsPlayingWhite:
8468 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8470 case TwoMachinesPlay:
8471 if (cps->twoMachinesColor[0] == 'w')
8472 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8474 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8481 } else if (strncmp(message, "opponent mates", 14) == 0) {
8483 case MachinePlaysBlack:
8484 case IcsPlayingBlack:
8485 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8487 case MachinePlaysWhite:
8488 case IcsPlayingWhite:
8489 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8491 case TwoMachinesPlay:
8492 if (cps->twoMachinesColor[0] == 'w')
8493 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8495 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8502 } else if (strncmp(message, "computer mates", 14) == 0) {
8504 case MachinePlaysBlack:
8505 case IcsPlayingBlack:
8506 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8508 case MachinePlaysWhite:
8509 case IcsPlayingWhite:
8510 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8512 case TwoMachinesPlay:
8513 if (cps->twoMachinesColor[0] == 'w')
8514 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8516 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8523 } else if (strncmp(message, "checkmate", 9) == 0) {
8524 if (WhiteOnMove(forwardMostMove)) {
8525 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8527 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8530 } else if (strstr(message, "Draw") != NULL ||
8531 strstr(message, "game is a draw") != NULL) {
8532 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8534 } else if (strstr(message, "offer") != NULL &&
8535 strstr(message, "draw") != NULL) {
8537 if (appData.zippyPlay && first.initDone) {
8538 /* Relay offer to ICS */
8539 SendToICS(ics_prefix);
8540 SendToICS("draw\n");
8543 cps->offeredDraw = 2; /* valid until this engine moves twice */
8544 if (gameMode == TwoMachinesPlay) {
8545 if (cps->other->offeredDraw) {
8546 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8547 /* [HGM] in two-machine mode we delay relaying draw offer */
8548 /* until after we also have move, to see if it is really claim */
8550 } else if (gameMode == MachinePlaysWhite ||
8551 gameMode == MachinePlaysBlack) {
8552 if (userOfferedDraw) {
8553 DisplayInformation(_("Machine accepts your draw offer"));
8554 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8556 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8563 * Look for thinking output
8565 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8566 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8568 int plylev, mvleft, mvtot, curscore, time;
8569 char mvname[MOVE_LEN];
8573 int prefixHint = FALSE;
8574 mvname[0] = NULLCHAR;
8577 case MachinePlaysBlack:
8578 case IcsPlayingBlack:
8579 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8581 case MachinePlaysWhite:
8582 case IcsPlayingWhite:
8583 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588 case IcsObserving: /* [DM] icsEngineAnalyze */
8589 if (!appData.icsEngineAnalyze) ignore = TRUE;
8591 case TwoMachinesPlay:
8592 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8602 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8604 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8605 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8607 if (plyext != ' ' && plyext != '\t') {
8611 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8612 if( cps->scoreIsAbsolute &&
8613 ( gameMode == MachinePlaysBlack ||
8614 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8615 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8616 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8617 !WhiteOnMove(currentMove)
8620 curscore = -curscore;
8623 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8625 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8628 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8629 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8630 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8631 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8632 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8633 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8635 } else DisplayError(_("failed writing PV"), 0);
8638 tempStats.depth = plylev;
8639 tempStats.nodes = nodes;
8640 tempStats.time = time;
8641 tempStats.score = curscore;
8642 tempStats.got_only_move = 0;
8644 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8647 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8648 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8649 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8650 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8651 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8652 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8653 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8654 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8657 /* Buffer overflow protection */
8658 if (pv[0] != NULLCHAR) {
8659 if (strlen(pv) >= sizeof(tempStats.movelist)
8660 && appData.debugMode) {
8662 "PV is too long; using the first %u bytes.\n",
8663 (unsigned) sizeof(tempStats.movelist) - 1);
8666 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8668 sprintf(tempStats.movelist, " no PV\n");
8671 if (tempStats.seen_stat) {
8672 tempStats.ok_to_send = 1;
8675 if (strchr(tempStats.movelist, '(') != NULL) {
8676 tempStats.line_is_book = 1;
8677 tempStats.nr_moves = 0;
8678 tempStats.moves_left = 0;
8680 tempStats.line_is_book = 0;
8683 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8684 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8686 SendProgramStatsToFrontend( cps, &tempStats );
8689 [AS] Protect the thinkOutput buffer from overflow... this
8690 is only useful if buf1 hasn't overflowed first!
8692 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8694 (gameMode == TwoMachinesPlay ?
8695 ToUpper(cps->twoMachinesColor[0]) : ' '),
8696 ((double) curscore) / 100.0,
8697 prefixHint ? lastHint : "",
8698 prefixHint ? " " : "" );
8700 if( buf1[0] != NULLCHAR ) {
8701 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8703 if( strlen(pv) > max_len ) {
8704 if( appData.debugMode) {
8705 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8707 pv[max_len+1] = '\0';
8710 strcat( thinkOutput, pv);
8713 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8714 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8715 DisplayMove(currentMove - 1);
8719 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8720 /* crafty (9.25+) says "(only move) <move>"
8721 * if there is only 1 legal move
8723 sscanf(p, "(only move) %s", buf1);
8724 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8725 sprintf(programStats.movelist, "%s (only move)", buf1);
8726 programStats.depth = 1;
8727 programStats.nr_moves = 1;
8728 programStats.moves_left = 1;
8729 programStats.nodes = 1;
8730 programStats.time = 1;
8731 programStats.got_only_move = 1;
8733 /* Not really, but we also use this member to
8734 mean "line isn't going to change" (Crafty
8735 isn't searching, so stats won't change) */
8736 programStats.line_is_book = 1;
8738 SendProgramStatsToFrontend( cps, &programStats );
8740 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8741 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8742 DisplayMove(currentMove - 1);
8745 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8746 &time, &nodes, &plylev, &mvleft,
8747 &mvtot, mvname) >= 5) {
8748 /* The stat01: line is from Crafty (9.29+) in response
8749 to the "." command */
8750 programStats.seen_stat = 1;
8751 cps->maybeThinking = TRUE;
8753 if (programStats.got_only_move || !appData.periodicUpdates)
8756 programStats.depth = plylev;
8757 programStats.time = time;
8758 programStats.nodes = nodes;
8759 programStats.moves_left = mvleft;
8760 programStats.nr_moves = mvtot;
8761 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8762 programStats.ok_to_send = 1;
8763 programStats.movelist[0] = '\0';
8765 SendProgramStatsToFrontend( cps, &programStats );
8769 } else if (strncmp(message,"++",2) == 0) {
8770 /* Crafty 9.29+ outputs this */
8771 programStats.got_fail = 2;
8774 } else if (strncmp(message,"--",2) == 0) {
8775 /* Crafty 9.29+ outputs this */
8776 programStats.got_fail = 1;
8779 } else if (thinkOutput[0] != NULLCHAR &&
8780 strncmp(message, " ", 4) == 0) {
8781 unsigned message_len;
8784 while (*p && *p == ' ') p++;
8786 message_len = strlen( p );
8788 /* [AS] Avoid buffer overflow */
8789 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8790 strcat(thinkOutput, " ");
8791 strcat(thinkOutput, p);
8794 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8795 strcat(programStats.movelist, " ");
8796 strcat(programStats.movelist, p);
8799 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8800 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8801 DisplayMove(currentMove - 1);
8809 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8810 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8812 ChessProgramStats cpstats;
8814 if (plyext != ' ' && plyext != '\t') {
8818 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8819 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8820 curscore = -curscore;
8823 cpstats.depth = plylev;
8824 cpstats.nodes = nodes;
8825 cpstats.time = time;
8826 cpstats.score = curscore;
8827 cpstats.got_only_move = 0;
8828 cpstats.movelist[0] = '\0';
8830 if (buf1[0] != NULLCHAR) {
8831 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8834 cpstats.ok_to_send = 0;
8835 cpstats.line_is_book = 0;
8836 cpstats.nr_moves = 0;
8837 cpstats.moves_left = 0;
8839 SendProgramStatsToFrontend( cps, &cpstats );
8846 /* Parse a game score from the character string "game", and
8847 record it as the history of the current game. The game
8848 score is NOT assumed to start from the standard position.
8849 The display is not updated in any way.
8852 ParseGameHistory (char *game)
8855 int fromX, fromY, toX, toY, boardIndex;
8860 if (appData.debugMode)
8861 fprintf(debugFP, "Parsing game history: %s\n", game);
8863 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8864 gameInfo.site = StrSave(appData.icsHost);
8865 gameInfo.date = PGNDate();
8866 gameInfo.round = StrSave("-");
8868 /* Parse out names of players */
8869 while (*game == ' ') game++;
8871 while (*game != ' ') *p++ = *game++;
8873 gameInfo.white = StrSave(buf);
8874 while (*game == ' ') game++;
8876 while (*game != ' ' && *game != '\n') *p++ = *game++;
8878 gameInfo.black = StrSave(buf);
8881 boardIndex = blackPlaysFirst ? 1 : 0;
8884 yyboardindex = boardIndex;
8885 moveType = (ChessMove) Myylex();
8887 case IllegalMove: /* maybe suicide chess, etc. */
8888 if (appData.debugMode) {
8889 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8890 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8891 setbuf(debugFP, NULL);
8893 case WhitePromotion:
8894 case BlackPromotion:
8895 case WhiteNonPromotion:
8896 case BlackNonPromotion:
8898 case WhiteCapturesEnPassant:
8899 case BlackCapturesEnPassant:
8900 case WhiteKingSideCastle:
8901 case WhiteQueenSideCastle:
8902 case BlackKingSideCastle:
8903 case BlackQueenSideCastle:
8904 case WhiteKingSideCastleWild:
8905 case WhiteQueenSideCastleWild:
8906 case BlackKingSideCastleWild:
8907 case BlackQueenSideCastleWild:
8909 case WhiteHSideCastleFR:
8910 case WhiteASideCastleFR:
8911 case BlackHSideCastleFR:
8912 case BlackASideCastleFR:
8914 fromX = currentMoveString[0] - AAA;
8915 fromY = currentMoveString[1] - ONE;
8916 toX = currentMoveString[2] - AAA;
8917 toY = currentMoveString[3] - ONE;
8918 promoChar = currentMoveString[4];
8922 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8923 fromX = moveType == WhiteDrop ?
8924 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8925 (int) CharToPiece(ToLower(currentMoveString[0]));
8927 toX = currentMoveString[2] - AAA;
8928 toY = currentMoveString[3] - ONE;
8929 promoChar = NULLCHAR;
8933 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8934 if (appData.debugMode) {
8935 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8936 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8937 setbuf(debugFP, NULL);
8939 DisplayError(buf, 0);
8941 case ImpossibleMove:
8943 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8944 if (appData.debugMode) {
8945 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8946 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947 setbuf(debugFP, NULL);
8949 DisplayError(buf, 0);
8952 if (boardIndex < backwardMostMove) {
8953 /* Oops, gap. How did that happen? */
8954 DisplayError(_("Gap in move list"), 0);
8957 backwardMostMove = blackPlaysFirst ? 1 : 0;
8958 if (boardIndex > forwardMostMove) {
8959 forwardMostMove = boardIndex;
8963 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8964 strcat(parseList[boardIndex-1], " ");
8965 strcat(parseList[boardIndex-1], yy_text);
8977 case GameUnfinished:
8978 if (gameMode == IcsExamining) {
8979 if (boardIndex < backwardMostMove) {
8980 /* Oops, gap. How did that happen? */
8983 backwardMostMove = blackPlaysFirst ? 1 : 0;
8986 gameInfo.result = moveType;
8987 p = strchr(yy_text, '{');
8988 if (p == NULL) p = strchr(yy_text, '(');
8991 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8993 q = strchr(p, *p == '{' ? '}' : ')');
8994 if (q != NULL) *q = NULLCHAR;
8997 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8998 gameInfo.resultDetails = StrSave(p);
9001 if (boardIndex >= forwardMostMove &&
9002 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9003 backwardMostMove = blackPlaysFirst ? 1 : 0;
9006 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9007 fromY, fromX, toY, toX, promoChar,
9008 parseList[boardIndex]);
9009 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9010 /* currentMoveString is set as a side-effect of yylex */
9011 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9012 strcat(moveList[boardIndex], "\n");
9014 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9015 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9021 if(gameInfo.variant != VariantShogi)
9022 strcat(parseList[boardIndex - 1], "+");
9026 strcat(parseList[boardIndex - 1], "#");
9033 /* Apply a move to the given board */
9035 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9037 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9038 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9040 /* [HGM] compute & store e.p. status and castling rights for new position */
9041 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9043 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9044 oldEP = (signed char)board[EP_STATUS];
9045 board[EP_STATUS] = EP_NONE;
9047 if (fromY == DROP_RANK) {
9049 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9050 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9053 piece = board[toY][toX] = (ChessSquare) fromX;
9057 if( board[toY][toX] != EmptySquare )
9058 board[EP_STATUS] = EP_CAPTURE;
9060 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9061 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9062 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9064 if( board[fromY][fromX] == WhitePawn ) {
9065 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9066 board[EP_STATUS] = EP_PAWN_MOVE;
9068 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9069 gameInfo.variant != VariantBerolina || toX < fromX)
9070 board[EP_STATUS] = toX | berolina;
9071 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9072 gameInfo.variant != VariantBerolina || toX > fromX)
9073 board[EP_STATUS] = toX;
9076 if( board[fromY][fromX] == BlackPawn ) {
9077 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9078 board[EP_STATUS] = EP_PAWN_MOVE;
9079 if( toY-fromY== -2) {
9080 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9081 gameInfo.variant != VariantBerolina || toX < fromX)
9082 board[EP_STATUS] = toX | berolina;
9083 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9084 gameInfo.variant != VariantBerolina || toX > fromX)
9085 board[EP_STATUS] = toX;
9089 for(i=0; i<nrCastlingRights; i++) {
9090 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9091 board[CASTLING][i] == toX && castlingRank[i] == toY
9092 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9095 if (fromX == toX && fromY == toY) return;
9097 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9098 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9099 if(gameInfo.variant == VariantKnightmate)
9100 king += (int) WhiteUnicorn - (int) WhiteKing;
9102 /* Code added by Tord: */
9103 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9104 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9105 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9106 board[fromY][fromX] = EmptySquare;
9107 board[toY][toX] = EmptySquare;
9108 if((toX > fromX) != (piece == WhiteRook)) {
9109 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9111 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9113 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9114 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9115 board[fromY][fromX] = EmptySquare;
9116 board[toY][toX] = EmptySquare;
9117 if((toX > fromX) != (piece == BlackRook)) {
9118 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9120 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9122 /* End of code added by Tord */
9124 } else if (board[fromY][fromX] == king
9125 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9126 && toY == fromY && toX > fromX+1) {
9127 board[fromY][fromX] = EmptySquare;
9128 board[toY][toX] = king;
9129 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9130 board[fromY][BOARD_RGHT-1] = EmptySquare;
9131 } else if (board[fromY][fromX] == king
9132 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9133 && toY == fromY && toX < fromX-1) {
9134 board[fromY][fromX] = EmptySquare;
9135 board[toY][toX] = king;
9136 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9137 board[fromY][BOARD_LEFT] = EmptySquare;
9138 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9139 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9140 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9142 /* white pawn promotion */
9143 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9144 if(gameInfo.variant==VariantBughouse ||
9145 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9146 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9147 board[fromY][fromX] = EmptySquare;
9148 } else if ((fromY >= BOARD_HEIGHT>>1)
9150 && gameInfo.variant != VariantXiangqi
9151 && gameInfo.variant != VariantBerolina
9152 && (board[fromY][fromX] == WhitePawn)
9153 && (board[toY][toX] == EmptySquare)) {
9154 board[fromY][fromX] = EmptySquare;
9155 board[toY][toX] = WhitePawn;
9156 captured = board[toY - 1][toX];
9157 board[toY - 1][toX] = EmptySquare;
9158 } else if ((fromY == BOARD_HEIGHT-4)
9160 && gameInfo.variant == VariantBerolina
9161 && (board[fromY][fromX] == WhitePawn)
9162 && (board[toY][toX] == EmptySquare)) {
9163 board[fromY][fromX] = EmptySquare;
9164 board[toY][toX] = WhitePawn;
9165 if(oldEP & EP_BEROLIN_A) {
9166 captured = board[fromY][fromX-1];
9167 board[fromY][fromX-1] = EmptySquare;
9168 }else{ captured = board[fromY][fromX+1];
9169 board[fromY][fromX+1] = EmptySquare;
9171 } else if (board[fromY][fromX] == king
9172 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173 && toY == fromY && toX > fromX+1) {
9174 board[fromY][fromX] = EmptySquare;
9175 board[toY][toX] = king;
9176 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177 board[fromY][BOARD_RGHT-1] = EmptySquare;
9178 } else if (board[fromY][fromX] == king
9179 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180 && toY == fromY && toX < fromX-1) {
9181 board[fromY][fromX] = EmptySquare;
9182 board[toY][toX] = king;
9183 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184 board[fromY][BOARD_LEFT] = EmptySquare;
9185 } else if (fromY == 7 && fromX == 3
9186 && board[fromY][fromX] == BlackKing
9187 && toY == 7 && toX == 5) {
9188 board[fromY][fromX] = EmptySquare;
9189 board[toY][toX] = BlackKing;
9190 board[fromY][7] = EmptySquare;
9191 board[toY][4] = BlackRook;
9192 } else if (fromY == 7 && fromX == 3
9193 && board[fromY][fromX] == BlackKing
9194 && toY == 7 && toX == 1) {
9195 board[fromY][fromX] = EmptySquare;
9196 board[toY][toX] = BlackKing;
9197 board[fromY][0] = EmptySquare;
9198 board[toY][2] = BlackRook;
9199 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9200 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9201 && toY < promoRank && promoChar
9203 /* black pawn promotion */
9204 board[toY][toX] = CharToPiece(ToLower(promoChar));
9205 if(gameInfo.variant==VariantBughouse ||
9206 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9207 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9208 board[fromY][fromX] = EmptySquare;
9209 } else if ((fromY < BOARD_HEIGHT>>1)
9211 && gameInfo.variant != VariantXiangqi
9212 && gameInfo.variant != VariantBerolina
9213 && (board[fromY][fromX] == BlackPawn)
9214 && (board[toY][toX] == EmptySquare)) {
9215 board[fromY][fromX] = EmptySquare;
9216 board[toY][toX] = BlackPawn;
9217 captured = board[toY + 1][toX];
9218 board[toY + 1][toX] = EmptySquare;
9219 } else if ((fromY == 3)
9221 && gameInfo.variant == VariantBerolina
9222 && (board[fromY][fromX] == BlackPawn)
9223 && (board[toY][toX] == EmptySquare)) {
9224 board[fromY][fromX] = EmptySquare;
9225 board[toY][toX] = BlackPawn;
9226 if(oldEP & EP_BEROLIN_A) {
9227 captured = board[fromY][fromX-1];
9228 board[fromY][fromX-1] = EmptySquare;
9229 }else{ captured = board[fromY][fromX+1];
9230 board[fromY][fromX+1] = EmptySquare;
9233 board[toY][toX] = board[fromY][fromX];
9234 board[fromY][fromX] = EmptySquare;
9238 if (gameInfo.holdingsWidth != 0) {
9240 /* !!A lot more code needs to be written to support holdings */
9241 /* [HGM] OK, so I have written it. Holdings are stored in the */
9242 /* penultimate board files, so they are automaticlly stored */
9243 /* in the game history. */
9244 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9245 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9246 /* Delete from holdings, by decreasing count */
9247 /* and erasing image if necessary */
9248 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9249 if(p < (int) BlackPawn) { /* white drop */
9250 p -= (int)WhitePawn;
9251 p = PieceToNumber((ChessSquare)p);
9252 if(p >= gameInfo.holdingsSize) p = 0;
9253 if(--board[p][BOARD_WIDTH-2] <= 0)
9254 board[p][BOARD_WIDTH-1] = EmptySquare;
9255 if((int)board[p][BOARD_WIDTH-2] < 0)
9256 board[p][BOARD_WIDTH-2] = 0;
9257 } else { /* black drop */
9258 p -= (int)BlackPawn;
9259 p = PieceToNumber((ChessSquare)p);
9260 if(p >= gameInfo.holdingsSize) p = 0;
9261 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9262 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9263 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9264 board[BOARD_HEIGHT-1-p][1] = 0;
9267 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9268 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9269 /* [HGM] holdings: Add to holdings, if holdings exist */
9270 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9271 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9272 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9275 if (p >= (int) BlackPawn) {
9276 p -= (int)BlackPawn;
9277 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9278 /* in Shogi restore piece to its original first */
9279 captured = (ChessSquare) (DEMOTED captured);
9282 p = PieceToNumber((ChessSquare)p);
9283 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9284 board[p][BOARD_WIDTH-2]++;
9285 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9287 p -= (int)WhitePawn;
9288 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9289 captured = (ChessSquare) (DEMOTED captured);
9292 p = PieceToNumber((ChessSquare)p);
9293 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9294 board[BOARD_HEIGHT-1-p][1]++;
9295 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9298 } else if (gameInfo.variant == VariantAtomic) {
9299 if (captured != EmptySquare) {
9301 for (y = toY-1; y <= toY+1; y++) {
9302 for (x = toX-1; x <= toX+1; x++) {
9303 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9304 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9305 board[y][x] = EmptySquare;
9309 board[toY][toX] = EmptySquare;
9312 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9313 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9315 if(promoChar == '+') {
9316 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9317 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9318 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9319 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9321 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9322 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9323 // [HGM] superchess: take promotion piece out of holdings
9324 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9325 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9326 if(!--board[k][BOARD_WIDTH-2])
9327 board[k][BOARD_WIDTH-1] = EmptySquare;
9329 if(!--board[BOARD_HEIGHT-1-k][1])
9330 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9336 /* Updates forwardMostMove */
9338 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9340 // forwardMostMove++; // [HGM] bare: moved downstream
9342 (void) CoordsToAlgebraic(boards[forwardMostMove],
9343 PosFlags(forwardMostMove),
9344 fromY, fromX, toY, toX, promoChar,
9345 parseList[forwardMostMove]);
9347 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9348 int timeLeft; static int lastLoadFlag=0; int king, piece;
9349 piece = boards[forwardMostMove][fromY][fromX];
9350 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9351 if(gameInfo.variant == VariantKnightmate)
9352 king += (int) WhiteUnicorn - (int) WhiteKing;
9353 if(forwardMostMove == 0) {
9354 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9355 fprintf(serverMoves, "%s;", UserName());
9356 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9357 fprintf(serverMoves, "%s;", second.tidy);
9358 fprintf(serverMoves, "%s;", first.tidy);
9359 if(gameMode == MachinePlaysWhite)
9360 fprintf(serverMoves, "%s;", UserName());
9361 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9362 fprintf(serverMoves, "%s;", second.tidy);
9363 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9364 lastLoadFlag = loadFlag;
9366 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9367 // print castling suffix
9368 if( toY == fromY && piece == king ) {
9370 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9372 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9375 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9376 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9377 boards[forwardMostMove][toY][toX] == EmptySquare
9378 && fromX != toX && fromY != toY)
9379 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9381 if(promoChar != NULLCHAR)
9382 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9384 char buf[MOVE_LEN*2], *p; int len;
9385 fprintf(serverMoves, "/%d/%d",
9386 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9387 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9388 else timeLeft = blackTimeRemaining/1000;
9389 fprintf(serverMoves, "/%d", timeLeft);
9390 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9391 if(p = strchr(buf, '=')) *p = NULLCHAR;
9392 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9393 fprintf(serverMoves, "/%s", buf);
9395 fflush(serverMoves);
9398 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9399 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9402 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9403 if (commentList[forwardMostMove+1] != NULL) {
9404 free(commentList[forwardMostMove+1]);
9405 commentList[forwardMostMove+1] = NULL;
9407 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9408 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9409 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9410 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9411 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9412 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9413 adjustedClock = FALSE;
9414 gameInfo.result = GameUnfinished;
9415 if (gameInfo.resultDetails != NULL) {
9416 free(gameInfo.resultDetails);
9417 gameInfo.resultDetails = NULL;
9419 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9420 moveList[forwardMostMove - 1]);
9421 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9427 if(gameInfo.variant != VariantShogi)
9428 strcat(parseList[forwardMostMove - 1], "+");
9432 strcat(parseList[forwardMostMove - 1], "#");
9435 if (appData.debugMode) {
9436 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9441 /* Updates currentMove if not pausing */
9443 ShowMove (int fromX, int fromY, int toX, int toY)
9445 int instant = (gameMode == PlayFromGameFile) ?
9446 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9447 if(appData.noGUI) return;
9448 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9450 if (forwardMostMove == currentMove + 1) {
9451 AnimateMove(boards[forwardMostMove - 1],
9452 fromX, fromY, toX, toY);
9454 if (appData.highlightLastMove) {
9455 SetHighlights(fromX, fromY, toX, toY);
9458 currentMove = forwardMostMove;
9461 if (instant) return;
9463 DisplayMove(currentMove - 1);
9464 DrawPosition(FALSE, boards[currentMove]);
9465 DisplayBothClocks();
9466 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9470 SendEgtPath (ChessProgramState *cps)
9471 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9472 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9474 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9477 char c, *q = name+1, *r, *s;
9479 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9480 while(*p && *p != ',') *q++ = *p++;
9482 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9483 strcmp(name, ",nalimov:") == 0 ) {
9484 // take nalimov path from the menu-changeable option first, if it is defined
9485 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9486 SendToProgram(buf,cps); // send egtbpath command for nalimov
9488 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9489 (s = StrStr(appData.egtFormats, name)) != NULL) {
9490 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9491 s = r = StrStr(s, ":") + 1; // beginning of path info
9492 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9493 c = *r; *r = 0; // temporarily null-terminate path info
9494 *--q = 0; // strip of trailig ':' from name
9495 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9497 SendToProgram(buf,cps); // send egtbpath command for this format
9499 if(*p == ',') p++; // read away comma to position for next format name
9504 InitChessProgram (ChessProgramState *cps, int setup)
9505 /* setup needed to setup FRC opening position */
9507 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9508 if (appData.noChessProgram) return;
9509 hintRequested = FALSE;
9510 bookRequested = FALSE;
9512 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9513 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9514 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9515 if(cps->memSize) { /* [HGM] memory */
9516 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9517 SendToProgram(buf, cps);
9519 SendEgtPath(cps); /* [HGM] EGT */
9520 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9521 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9522 SendToProgram(buf, cps);
9525 SendToProgram(cps->initString, cps);
9526 if (gameInfo.variant != VariantNormal &&
9527 gameInfo.variant != VariantLoadable
9528 /* [HGM] also send variant if board size non-standard */
9529 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9531 char *v = VariantName(gameInfo.variant);
9532 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9533 /* [HGM] in protocol 1 we have to assume all variants valid */
9534 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9535 DisplayFatalError(buf, 0, 1);
9539 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9540 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9541 if( gameInfo.variant == VariantXiangqi )
9542 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9543 if( gameInfo.variant == VariantShogi )
9544 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9545 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9546 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9547 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9548 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9549 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9550 if( gameInfo.variant == VariantCourier )
9551 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9552 if( gameInfo.variant == VariantSuper )
9553 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9554 if( gameInfo.variant == VariantGreat )
9555 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9556 if( gameInfo.variant == VariantSChess )
9557 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9558 if( gameInfo.variant == VariantGrand )
9559 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9562 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9563 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9564 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9565 if(StrStr(cps->variants, b) == NULL) {
9566 // specific sized variant not known, check if general sizing allowed
9567 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9568 if(StrStr(cps->variants, "boardsize") == NULL) {
9569 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9570 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9571 DisplayFatalError(buf, 0, 1);
9574 /* [HGM] here we really should compare with the maximum supported board size */
9577 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9578 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9579 SendToProgram(buf, cps);
9581 currentlyInitializedVariant = gameInfo.variant;
9583 /* [HGM] send opening position in FRC to first engine */
9585 SendToProgram("force\n", cps);
9587 /* engine is now in force mode! Set flag to wake it up after first move. */
9588 setboardSpoiledMachineBlack = 1;
9592 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9593 SendToProgram(buf, cps);
9595 cps->maybeThinking = FALSE;
9596 cps->offeredDraw = 0;
9597 if (!appData.icsActive) {
9598 SendTimeControl(cps, movesPerSession, timeControl,
9599 timeIncrement, appData.searchDepth,
9602 if (appData.showThinking
9603 // [HGM] thinking: four options require thinking output to be sent
9604 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9606 SendToProgram("post\n", cps);
9608 SendToProgram("hard\n", cps);
9609 if (!appData.ponderNextMove) {
9610 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9611 it without being sure what state we are in first. "hard"
9612 is not a toggle, so that one is OK.
9614 SendToProgram("easy\n", cps);
9617 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9618 SendToProgram(buf, cps);
9620 cps->initDone = TRUE;
9621 ClearEngineOutputPane(cps == &second);
9626 StartChessProgram (ChessProgramState *cps)
9631 if (appData.noChessProgram) return;
9632 cps->initDone = FALSE;
9634 if (strcmp(cps->host, "localhost") == 0) {
9635 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9636 } else if (*appData.remoteShell == NULLCHAR) {
9637 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9639 if (*appData.remoteUser == NULLCHAR) {
9640 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9643 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9644 cps->host, appData.remoteUser, cps->program);
9646 err = StartChildProcess(buf, "", &cps->pr);
9650 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9651 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9652 if(cps != &first) return;
9653 appData.noChessProgram = TRUE;
9656 // DisplayFatalError(buf, err, 1);
9657 // cps->pr = NoProc;
9662 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9663 if (cps->protocolVersion > 1) {
9664 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9665 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9666 cps->comboCnt = 0; // and values of combo boxes
9667 SendToProgram(buf, cps);
9669 SendToProgram("xboard\n", cps);
9674 TwoMachinesEventIfReady P((void))
9676 static int curMess = 0;
9677 if (first.lastPing != first.lastPong) {
9678 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9679 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9682 if (second.lastPing != second.lastPong) {
9683 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9684 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9687 DisplayMessage("", ""); curMess = 0;
9693 MakeName (char *template)
9697 static char buf[MSG_SIZ];
9701 clock = time((time_t *)NULL);
9702 tm = localtime(&clock);
9704 while(*p++ = *template++) if(p[-1] == '%') {
9705 switch(*template++) {
9706 case 0: *p = 0; return buf;
9707 case 'Y': i = tm->tm_year+1900; break;
9708 case 'y': i = tm->tm_year-100; break;
9709 case 'M': i = tm->tm_mon+1; break;
9710 case 'd': i = tm->tm_mday; break;
9711 case 'h': i = tm->tm_hour; break;
9712 case 'm': i = tm->tm_min; break;
9713 case 's': i = tm->tm_sec; break;
9716 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9722 CountPlayers (char *p)
9725 while(p = strchr(p, '\n')) p++, n++; // count participants
9730 WriteTourneyFile (char *results, FILE *f)
9731 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9732 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9733 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9734 // create a file with tournament description
9735 fprintf(f, "-participants {%s}\n", appData.participants);
9736 fprintf(f, "-seedBase %d\n", appData.seedBase);
9737 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9738 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9739 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9740 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9741 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9742 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9743 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9744 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9745 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9746 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9747 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9748 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9750 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9752 fprintf(f, "-mps %d\n", appData.movesPerSession);
9753 fprintf(f, "-tc %s\n", appData.timeControl);
9754 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9756 fprintf(f, "-results \"%s\"\n", results);
9761 #define MAXENGINES 1000
9762 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9765 Substitute (char *participants, int expunge)
9767 int i, changed, changes=0, nPlayers=0;
9768 char *p, *q, *r, buf[MSG_SIZ];
9769 if(participants == NULL) return;
9770 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9771 r = p = participants; q = appData.participants;
9772 while(*p && *p == *q) {
9773 if(*p == '\n') r = p+1, nPlayers++;
9776 if(*p) { // difference
9777 while(*p && *p++ != '\n');
9778 while(*q && *q++ != '\n');
9780 changes = 1 + (strcmp(p, q) != 0);
9782 if(changes == 1) { // a single engine mnemonic was changed
9783 q = r; while(*q) nPlayers += (*q++ == '\n');
9784 p = buf; while(*r && (*p = *r++) != '\n') p++;
9786 NamesToList(firstChessProgramNames, command, mnemonic);
9787 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9788 if(mnemonic[i]) { // The substitute is valid
9790 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9791 flock(fileno(f), LOCK_EX);
9792 ParseArgsFromFile(f);
9793 fseek(f, 0, SEEK_SET);
9794 FREE(appData.participants); appData.participants = participants;
9795 if(expunge) { // erase results of replaced engine
9796 int len = strlen(appData.results), w, b, dummy;
9797 for(i=0; i<len; i++) {
9798 Pairing(i, nPlayers, &w, &b, &dummy);
9799 if((w == changed || b == changed) && appData.results[i] == '*') {
9800 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9805 for(i=0; i<len; i++) {
9806 Pairing(i, nPlayers, &w, &b, &dummy);
9807 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9810 WriteTourneyFile(appData.results, f);
9811 fclose(f); // release lock
9814 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9816 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9817 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9823 CreateTourney (char *name)
9826 if(matchMode && strcmp(name, appData.tourneyFile)) {
9827 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9829 if(name[0] == NULLCHAR) {
9830 if(appData.participants[0])
9831 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9834 f = fopen(name, "r");
9835 if(f) { // file exists
9836 ASSIGN(appData.tourneyFile, name);
9837 ParseArgsFromFile(f); // parse it
9839 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9840 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9841 DisplayError(_("Not enough participants"), 0);
9844 ASSIGN(appData.tourneyFile, name);
9845 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9846 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9849 appData.noChessProgram = FALSE;
9850 appData.clockMode = TRUE;
9856 NamesToList (char *names, char **engineList, char **engineMnemonic)
9858 char buf[MSG_SIZ], *p, *q;
9862 while(*p && *p != '\n') *q++ = *p++;
9864 if(engineList[i]) free(engineList[i]);
9865 engineList[i] = strdup(buf);
9867 TidyProgramName(engineList[i], "localhost", buf);
9868 if(engineMnemonic[i]) free(engineMnemonic[i]);
9869 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9871 sscanf(q + 8, "%s", buf + strlen(buf));
9874 engineMnemonic[i] = strdup(buf);
9876 if(i > MAXENGINES - 2) break;
9878 engineList[i] = engineMnemonic[i] = NULL;
9881 // following implemented as macro to avoid type limitations
9882 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9886 { // swap settings for first engine and other engine (so far only some selected options)
9891 SWAP(chessProgram, p)
9893 SWAP(hasOwnBookUCI, h)
9894 SWAP(protocolVersion, h)
9896 SWAP(scoreIsAbsolute, h)
9905 SetPlayer (int player)
9906 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9908 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9909 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9910 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9911 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9913 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9914 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9915 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9916 ParseArgsFromString(buf);
9922 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9923 { // determine players from game number
9924 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9926 if(appData.tourneyType == 0) {
9927 roundsPerCycle = (nPlayers - 1) | 1;
9928 pairingsPerRound = nPlayers / 2;
9929 } else if(appData.tourneyType > 0) {
9930 roundsPerCycle = nPlayers - appData.tourneyType;
9931 pairingsPerRound = appData.tourneyType;
9933 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9934 gamesPerCycle = gamesPerRound * roundsPerCycle;
9935 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9936 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9937 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9938 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9939 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9940 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9942 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9943 if(appData.roundSync) *syncInterval = gamesPerRound;
9945 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9947 if(appData.tourneyType == 0) {
9948 if(curPairing == (nPlayers-1)/2 ) {
9949 *whitePlayer = curRound;
9950 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9952 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9953 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9954 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9955 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9957 } else if(appData.tourneyType > 0) {
9958 *whitePlayer = curPairing;
9959 *blackPlayer = curRound + appData.tourneyType;
9962 // take care of white/black alternation per round.
9963 // For cycles and games this is already taken care of by default, derived from matchGame!
9964 return curRound & 1;
9968 NextTourneyGame (int nr, int *swapColors)
9969 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9971 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9973 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9974 tf = fopen(appData.tourneyFile, "r");
9975 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9976 ParseArgsFromFile(tf); fclose(tf);
9977 InitTimeControls(); // TC might be altered from tourney file
9979 nPlayers = CountPlayers(appData.participants); // count participants
9980 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9981 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9984 p = q = appData.results;
9985 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9986 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9987 DisplayMessage(_("Waiting for other game(s)"),"");
9988 waitingForGame = TRUE;
9989 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9992 waitingForGame = FALSE;
9995 if(appData.tourneyType < 0) {
9996 if(nr>=0 && !pairingReceived) {
9998 if(pairing.pr == NoProc) {
9999 if(!appData.pairingEngine[0]) {
10000 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10003 StartChessProgram(&pairing); // starts the pairing engine
10005 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10006 SendToProgram(buf, &pairing);
10007 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10008 SendToProgram(buf, &pairing);
10009 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10011 pairingReceived = 0; // ... so we continue here
10013 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10014 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10015 matchGame = 1; roundNr = nr / syncInterval + 1;
10018 if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10020 // redefine engines, engine dir, etc.
10021 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10022 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10024 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10025 SwapEngines(1); // and make that valid for second engine by swapping
10026 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10027 InitEngine(&second, 1);
10028 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10029 UpdateLogos(FALSE); // leave display to ModeHiglight()
10035 { // performs game initialization that does not invoke engines, and then tries to start the game
10036 int res, firstWhite, swapColors = 0;
10037 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10038 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10039 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10040 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10041 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10042 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10043 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10044 Reset(FALSE, first.pr != NoProc);
10045 res = LoadGameOrPosition(matchGame); // setup game
10046 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10047 if(!res) return; // abort when bad game/pos file
10048 TwoMachinesEvent();
10052 UserAdjudicationEvent (int result)
10054 ChessMove gameResult = GameIsDrawn;
10057 gameResult = WhiteWins;
10059 else if( result < 0 ) {
10060 gameResult = BlackWins;
10063 if( gameMode == TwoMachinesPlay ) {
10064 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10069 // [HGM] save: calculate checksum of game to make games easily identifiable
10071 StringCheckSum (char *s)
10074 if(s==NULL) return 0;
10075 while(*s) i = i*259 + *s++;
10083 for(i=backwardMostMove; i<forwardMostMove; i++) {
10084 sum += pvInfoList[i].depth;
10085 sum += StringCheckSum(parseList[i]);
10086 sum += StringCheckSum(commentList[i]);
10089 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10090 return sum + StringCheckSum(commentList[i]);
10091 } // end of save patch
10094 GameEnds (ChessMove result, char *resultDetails, int whosays)
10096 GameMode nextGameMode;
10098 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10100 if(endingGame) return; /* [HGM] crash: forbid recursion */
10102 if(twoBoards) { // [HGM] dual: switch back to one board
10103 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10104 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10106 if (appData.debugMode) {
10107 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10108 result, resultDetails ? resultDetails : "(null)", whosays);
10111 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10113 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10114 /* If we are playing on ICS, the server decides when the
10115 game is over, but the engine can offer to draw, claim
10119 if (appData.zippyPlay && first.initDone) {
10120 if (result == GameIsDrawn) {
10121 /* In case draw still needs to be claimed */
10122 SendToICS(ics_prefix);
10123 SendToICS("draw\n");
10124 } else if (StrCaseStr(resultDetails, "resign")) {
10125 SendToICS(ics_prefix);
10126 SendToICS("resign\n");
10130 endingGame = 0; /* [HGM] crash */
10134 /* If we're loading the game from a file, stop */
10135 if (whosays == GE_FILE) {
10136 (void) StopLoadGameTimer();
10140 /* Cancel draw offers */
10141 first.offeredDraw = second.offeredDraw = 0;
10143 /* If this is an ICS game, only ICS can really say it's done;
10144 if not, anyone can. */
10145 isIcsGame = (gameMode == IcsPlayingWhite ||
10146 gameMode == IcsPlayingBlack ||
10147 gameMode == IcsObserving ||
10148 gameMode == IcsExamining);
10150 if (!isIcsGame || whosays == GE_ICS) {
10151 /* OK -- not an ICS game, or ICS said it was done */
10153 if (!isIcsGame && !appData.noChessProgram)
10154 SetUserThinkingEnables();
10156 /* [HGM] if a machine claims the game end we verify this claim */
10157 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10158 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10160 ChessMove trueResult = (ChessMove) -1;
10162 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10163 first.twoMachinesColor[0] :
10164 second.twoMachinesColor[0] ;
10166 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10167 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10168 /* [HGM] verify: engine mate claims accepted if they were flagged */
10169 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10171 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10172 /* [HGM] verify: engine mate claims accepted if they were flagged */
10173 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10175 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10176 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10179 // now verify win claims, but not in drop games, as we don't understand those yet
10180 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10181 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10182 (result == WhiteWins && claimer == 'w' ||
10183 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10184 if (appData.debugMode) {
10185 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10186 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10188 if(result != trueResult) {
10189 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10190 result = claimer == 'w' ? BlackWins : WhiteWins;
10191 resultDetails = buf;
10194 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10195 && (forwardMostMove <= backwardMostMove ||
10196 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10197 (claimer=='b')==(forwardMostMove&1))
10199 /* [HGM] verify: draws that were not flagged are false claims */
10200 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10201 result = claimer == 'w' ? BlackWins : WhiteWins;
10202 resultDetails = buf;
10204 /* (Claiming a loss is accepted no questions asked!) */
10206 /* [HGM] bare: don't allow bare King to win */
10207 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10208 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10209 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10210 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10211 && result != GameIsDrawn)
10212 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10213 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10214 int p = (signed char)boards[forwardMostMove][i][j] - color;
10215 if(p >= 0 && p <= (int)WhiteKing) k++;
10217 if (appData.debugMode) {
10218 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10219 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10222 result = GameIsDrawn;
10223 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10224 resultDetails = buf;
10230 if(serverMoves != NULL && !loadFlag) { char c = '=';
10231 if(result==WhiteWins) c = '+';
10232 if(result==BlackWins) c = '-';
10233 if(resultDetails != NULL)
10234 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10236 if (resultDetails != NULL) {
10237 gameInfo.result = result;
10238 gameInfo.resultDetails = StrSave(resultDetails);
10240 /* display last move only if game was not loaded from file */
10241 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10242 DisplayMove(currentMove - 1);
10244 if (forwardMostMove != 0) {
10245 if (gameMode != PlayFromGameFile && gameMode != EditGame
10246 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10248 if (*appData.saveGameFile != NULLCHAR) {
10249 SaveGameToFile(appData.saveGameFile, TRUE);
10250 } else if (appData.autoSaveGames) {
10253 if (*appData.savePositionFile != NULLCHAR) {
10254 SavePositionToFile(appData.savePositionFile);
10259 /* Tell program how game ended in case it is learning */
10260 /* [HGM] Moved this to after saving the PGN, just in case */
10261 /* engine died and we got here through time loss. In that */
10262 /* case we will get a fatal error writing the pipe, which */
10263 /* would otherwise lose us the PGN. */
10264 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10265 /* output during GameEnds should never be fatal anymore */
10266 if (gameMode == MachinePlaysWhite ||
10267 gameMode == MachinePlaysBlack ||
10268 gameMode == TwoMachinesPlay ||
10269 gameMode == IcsPlayingWhite ||
10270 gameMode == IcsPlayingBlack ||
10271 gameMode == BeginningOfGame) {
10273 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10275 if (first.pr != NoProc) {
10276 SendToProgram(buf, &first);
10278 if (second.pr != NoProc &&
10279 gameMode == TwoMachinesPlay) {
10280 SendToProgram(buf, &second);
10285 if (appData.icsActive) {
10286 if (appData.quietPlay &&
10287 (gameMode == IcsPlayingWhite ||
10288 gameMode == IcsPlayingBlack)) {
10289 SendToICS(ics_prefix);
10290 SendToICS("set shout 1\n");
10292 nextGameMode = IcsIdle;
10293 ics_user_moved = FALSE;
10294 /* clean up premove. It's ugly when the game has ended and the
10295 * premove highlights are still on the board.
10298 gotPremove = FALSE;
10299 ClearPremoveHighlights();
10300 DrawPosition(FALSE, boards[currentMove]);
10302 if (whosays == GE_ICS) {
10305 if (gameMode == IcsPlayingWhite)
10307 else if(gameMode == IcsPlayingBlack)
10308 PlayIcsLossSound();
10311 if (gameMode == IcsPlayingBlack)
10313 else if(gameMode == IcsPlayingWhite)
10314 PlayIcsLossSound();
10317 PlayIcsDrawSound();
10320 PlayIcsUnfinishedSound();
10323 } else if (gameMode == EditGame ||
10324 gameMode == PlayFromGameFile ||
10325 gameMode == AnalyzeMode ||
10326 gameMode == AnalyzeFile) {
10327 nextGameMode = gameMode;
10329 nextGameMode = EndOfGame;
10334 nextGameMode = gameMode;
10337 if (appData.noChessProgram) {
10338 gameMode = nextGameMode;
10340 endingGame = 0; /* [HGM] crash */
10345 /* Put first chess program into idle state */
10346 if (first.pr != NoProc &&
10347 (gameMode == MachinePlaysWhite ||
10348 gameMode == MachinePlaysBlack ||
10349 gameMode == TwoMachinesPlay ||
10350 gameMode == IcsPlayingWhite ||
10351 gameMode == IcsPlayingBlack ||
10352 gameMode == BeginningOfGame)) {
10353 SendToProgram("force\n", &first);
10354 if (first.usePing) {
10356 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10357 SendToProgram(buf, &first);
10360 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10361 /* Kill off first chess program */
10362 if (first.isr != NULL)
10363 RemoveInputSource(first.isr);
10366 if (first.pr != NoProc) {
10368 DoSleep( appData.delayBeforeQuit );
10369 SendToProgram("quit\n", &first);
10370 DoSleep( appData.delayAfterQuit );
10371 DestroyChildProcess(first.pr, first.useSigterm);
10375 if (second.reuse) {
10376 /* Put second chess program into idle state */
10377 if (second.pr != NoProc &&
10378 gameMode == TwoMachinesPlay) {
10379 SendToProgram("force\n", &second);
10380 if (second.usePing) {
10382 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10383 SendToProgram(buf, &second);
10386 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10387 /* Kill off second chess program */
10388 if (second.isr != NULL)
10389 RemoveInputSource(second.isr);
10392 if (second.pr != NoProc) {
10393 DoSleep( appData.delayBeforeQuit );
10394 SendToProgram("quit\n", &second);
10395 DoSleep( appData.delayAfterQuit );
10396 DestroyChildProcess(second.pr, second.useSigterm);
10398 second.pr = NoProc;
10401 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10402 char resChar = '=';
10406 if (first.twoMachinesColor[0] == 'w') {
10409 second.matchWins++;
10414 if (first.twoMachinesColor[0] == 'b') {
10417 second.matchWins++;
10420 case GameUnfinished:
10426 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10427 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10428 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10429 ReserveGame(nextGame, resChar); // sets nextGame
10430 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10431 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10432 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10434 if (nextGame <= appData.matchGames && !abortMatch) {
10435 gameMode = nextGameMode;
10436 matchGame = nextGame; // this will be overruled in tourney mode!
10437 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10438 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10439 endingGame = 0; /* [HGM] crash */
10442 gameMode = nextGameMode;
10443 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10444 first.tidy, second.tidy,
10445 first.matchWins, second.matchWins,
10446 appData.matchGames - (first.matchWins + second.matchWins));
10447 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10448 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10449 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10450 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10451 first.twoMachinesColor = "black\n";
10452 second.twoMachinesColor = "white\n";
10454 first.twoMachinesColor = "white\n";
10455 second.twoMachinesColor = "black\n";
10459 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10460 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10462 gameMode = nextGameMode;
10464 endingGame = 0; /* [HGM] crash */
10465 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10466 if(matchMode == TRUE) { // match through command line: exit with or without popup
10468 ToNrEvent(forwardMostMove);
10469 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10471 } else DisplayFatalError(buf, 0, 0);
10472 } else { // match through menu; just stop, with or without popup
10473 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10476 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10477 } else DisplayNote(buf);
10479 if(ranking) free(ranking);
10483 /* Assumes program was just initialized (initString sent).
10484 Leaves program in force mode. */
10486 FeedMovesToProgram (ChessProgramState *cps, int upto)
10490 if (appData.debugMode)
10491 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10492 startedFromSetupPosition ? "position and " : "",
10493 backwardMostMove, upto, cps->which);
10494 if(currentlyInitializedVariant != gameInfo.variant) {
10496 // [HGM] variantswitch: make engine aware of new variant
10497 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10498 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10499 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10500 SendToProgram(buf, cps);
10501 currentlyInitializedVariant = gameInfo.variant;
10503 SendToProgram("force\n", cps);
10504 if (startedFromSetupPosition) {
10505 SendBoard(cps, backwardMostMove);
10506 if (appData.debugMode) {
10507 fprintf(debugFP, "feedMoves\n");
10510 for (i = backwardMostMove; i < upto; i++) {
10511 SendMoveToProgram(i, cps);
10517 ResurrectChessProgram ()
10519 /* The chess program may have exited.
10520 If so, restart it and feed it all the moves made so far. */
10521 static int doInit = 0;
10523 if (appData.noChessProgram) return 1;
10525 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10526 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10527 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10528 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10530 if (first.pr != NoProc) return 1;
10531 StartChessProgram(&first);
10533 InitChessProgram(&first, FALSE);
10534 FeedMovesToProgram(&first, currentMove);
10536 if (!first.sendTime) {
10537 /* can't tell gnuchess what its clock should read,
10538 so we bow to its notion. */
10540 timeRemaining[0][currentMove] = whiteTimeRemaining;
10541 timeRemaining[1][currentMove] = blackTimeRemaining;
10544 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10545 appData.icsEngineAnalyze) && first.analysisSupport) {
10546 SendToProgram("analyze\n", &first);
10547 first.analyzing = TRUE;
10553 * Button procedures
10556 Reset (int redraw, int init)
10560 if (appData.debugMode) {
10561 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10562 redraw, init, gameMode);
10564 CleanupTail(); // [HGM] vari: delete any stored variations
10565 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10566 pausing = pauseExamInvalid = FALSE;
10567 startedFromSetupPosition = blackPlaysFirst = FALSE;
10569 whiteFlag = blackFlag = FALSE;
10570 userOfferedDraw = FALSE;
10571 hintRequested = bookRequested = FALSE;
10572 first.maybeThinking = FALSE;
10573 second.maybeThinking = FALSE;
10574 first.bookSuspend = FALSE; // [HGM] book
10575 second.bookSuspend = FALSE;
10576 thinkOutput[0] = NULLCHAR;
10577 lastHint[0] = NULLCHAR;
10578 ClearGameInfo(&gameInfo);
10579 gameInfo.variant = StringToVariant(appData.variant);
10580 ics_user_moved = ics_clock_paused = FALSE;
10581 ics_getting_history = H_FALSE;
10583 white_holding[0] = black_holding[0] = NULLCHAR;
10584 ClearProgramStats();
10585 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10589 flipView = appData.flipView;
10590 ClearPremoveHighlights();
10591 gotPremove = FALSE;
10592 alarmSounded = FALSE;
10594 GameEnds(EndOfFile, NULL, GE_PLAYER);
10595 if(appData.serverMovesName != NULL) {
10596 /* [HGM] prepare to make moves file for broadcasting */
10597 clock_t t = clock();
10598 if(serverMoves != NULL) fclose(serverMoves);
10599 serverMoves = fopen(appData.serverMovesName, "r");
10600 if(serverMoves != NULL) {
10601 fclose(serverMoves);
10602 /* delay 15 sec before overwriting, so all clients can see end */
10603 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10605 serverMoves = fopen(appData.serverMovesName, "w");
10609 gameMode = BeginningOfGame;
10611 if(appData.icsActive) gameInfo.variant = VariantNormal;
10612 currentMove = forwardMostMove = backwardMostMove = 0;
10613 MarkTargetSquares(1);
10614 InitPosition(redraw);
10615 for (i = 0; i < MAX_MOVES; i++) {
10616 if (commentList[i] != NULL) {
10617 free(commentList[i]);
10618 commentList[i] = NULL;
10622 timeRemaining[0][0] = whiteTimeRemaining;
10623 timeRemaining[1][0] = blackTimeRemaining;
10625 if (first.pr == NoProc) {
10626 StartChessProgram(&first);
10629 InitChessProgram(&first, startedFromSetupPosition);
10632 DisplayMessage("", "");
10633 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10634 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10638 AutoPlayGameLoop ()
10641 if (!AutoPlayOneMove())
10643 if (matchMode || appData.timeDelay == 0)
10645 if (appData.timeDelay < 0)
10647 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10656 int fromX, fromY, toX, toY;
10658 if (appData.debugMode) {
10659 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10662 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10665 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10666 pvInfoList[currentMove].depth = programStats.depth;
10667 pvInfoList[currentMove].score = programStats.score;
10668 pvInfoList[currentMove].time = 0;
10669 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10672 if (currentMove >= forwardMostMove) {
10673 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10674 // gameMode = EndOfGame;
10675 // ModeHighlight();
10677 /* [AS] Clear current move marker at the end of a game */
10678 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10683 toX = moveList[currentMove][2] - AAA;
10684 toY = moveList[currentMove][3] - ONE;
10686 if (moveList[currentMove][1] == '@') {
10687 if (appData.highlightLastMove) {
10688 SetHighlights(-1, -1, toX, toY);
10691 fromX = moveList[currentMove][0] - AAA;
10692 fromY = moveList[currentMove][1] - ONE;
10694 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10696 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10698 if (appData.highlightLastMove) {
10699 SetHighlights(fromX, fromY, toX, toY);
10702 DisplayMove(currentMove);
10703 SendMoveToProgram(currentMove++, &first);
10704 DisplayBothClocks();
10705 DrawPosition(FALSE, boards[currentMove]);
10706 // [HGM] PV info: always display, routine tests if empty
10707 DisplayComment(currentMove - 1, commentList[currentMove]);
10713 LoadGameOneMove (ChessMove readAhead)
10715 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10716 char promoChar = NULLCHAR;
10717 ChessMove moveType;
10718 char move[MSG_SIZ];
10721 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10722 gameMode != AnalyzeMode && gameMode != Training) {
10727 yyboardindex = forwardMostMove;
10728 if (readAhead != EndOfFile) {
10729 moveType = readAhead;
10731 if (gameFileFP == NULL)
10733 moveType = (ChessMove) Myylex();
10737 switch (moveType) {
10739 if (appData.debugMode)
10740 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10743 /* append the comment but don't display it */
10744 AppendComment(currentMove, p, FALSE);
10747 case WhiteCapturesEnPassant:
10748 case BlackCapturesEnPassant:
10749 case WhitePromotion:
10750 case BlackPromotion:
10751 case WhiteNonPromotion:
10752 case BlackNonPromotion:
10754 case WhiteKingSideCastle:
10755 case WhiteQueenSideCastle:
10756 case BlackKingSideCastle:
10757 case BlackQueenSideCastle:
10758 case WhiteKingSideCastleWild:
10759 case WhiteQueenSideCastleWild:
10760 case BlackKingSideCastleWild:
10761 case BlackQueenSideCastleWild:
10763 case WhiteHSideCastleFR:
10764 case WhiteASideCastleFR:
10765 case BlackHSideCastleFR:
10766 case BlackASideCastleFR:
10768 if (appData.debugMode)
10769 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10770 fromX = currentMoveString[0] - AAA;
10771 fromY = currentMoveString[1] - ONE;
10772 toX = currentMoveString[2] - AAA;
10773 toY = currentMoveString[3] - ONE;
10774 promoChar = currentMoveString[4];
10779 if (appData.debugMode)
10780 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10781 fromX = moveType == WhiteDrop ?
10782 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10783 (int) CharToPiece(ToLower(currentMoveString[0]));
10785 toX = currentMoveString[2] - AAA;
10786 toY = currentMoveString[3] - ONE;
10792 case GameUnfinished:
10793 if (appData.debugMode)
10794 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10795 p = strchr(yy_text, '{');
10796 if (p == NULL) p = strchr(yy_text, '(');
10799 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10801 q = strchr(p, *p == '{' ? '}' : ')');
10802 if (q != NULL) *q = NULLCHAR;
10805 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10806 GameEnds(moveType, p, GE_FILE);
10808 if (cmailMsgLoaded) {
10810 flipView = WhiteOnMove(currentMove);
10811 if (moveType == GameUnfinished) flipView = !flipView;
10812 if (appData.debugMode)
10813 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10818 if (appData.debugMode)
10819 fprintf(debugFP, "Parser hit end of file\n");
10820 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10826 if (WhiteOnMove(currentMove)) {
10827 GameEnds(BlackWins, "Black mates", GE_FILE);
10829 GameEnds(WhiteWins, "White mates", GE_FILE);
10833 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10839 case MoveNumberOne:
10840 if (lastLoadGameStart == GNUChessGame) {
10841 /* GNUChessGames have numbers, but they aren't move numbers */
10842 if (appData.debugMode)
10843 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10844 yy_text, (int) moveType);
10845 return LoadGameOneMove(EndOfFile); /* tail recursion */
10847 /* else fall thru */
10852 /* Reached start of next game in file */
10853 if (appData.debugMode)
10854 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10855 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10861 if (WhiteOnMove(currentMove)) {
10862 GameEnds(BlackWins, "Black mates", GE_FILE);
10864 GameEnds(WhiteWins, "White mates", GE_FILE);
10868 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10874 case PositionDiagram: /* should not happen; ignore */
10875 case ElapsedTime: /* ignore */
10876 case NAG: /* ignore */
10877 if (appData.debugMode)
10878 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10879 yy_text, (int) moveType);
10880 return LoadGameOneMove(EndOfFile); /* tail recursion */
10883 if (appData.testLegality) {
10884 if (appData.debugMode)
10885 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10886 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10887 (forwardMostMove / 2) + 1,
10888 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10889 DisplayError(move, 0);
10892 if (appData.debugMode)
10893 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10894 yy_text, currentMoveString);
10895 fromX = currentMoveString[0] - AAA;
10896 fromY = currentMoveString[1] - ONE;
10897 toX = currentMoveString[2] - AAA;
10898 toY = currentMoveString[3] - ONE;
10899 promoChar = currentMoveString[4];
10903 case AmbiguousMove:
10904 if (appData.debugMode)
10905 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10906 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10907 (forwardMostMove / 2) + 1,
10908 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10909 DisplayError(move, 0);
10914 case ImpossibleMove:
10915 if (appData.debugMode)
10916 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10917 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10918 (forwardMostMove / 2) + 1,
10919 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920 DisplayError(move, 0);
10926 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10927 DrawPosition(FALSE, boards[currentMove]);
10928 DisplayBothClocks();
10929 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10930 DisplayComment(currentMove - 1, commentList[currentMove]);
10932 (void) StopLoadGameTimer();
10934 cmailOldMove = forwardMostMove;
10937 /* currentMoveString is set as a side-effect of yylex */
10939 thinkOutput[0] = NULLCHAR;
10940 MakeMove(fromX, fromY, toX, toY, promoChar);
10941 currentMove = forwardMostMove;
10946 /* Load the nth game from the given file */
10948 LoadGameFromFile (char *filename, int n, char *title, int useList)
10953 if (strcmp(filename, "-") == 0) {
10957 f = fopen(filename, "rb");
10959 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10960 DisplayError(buf, errno);
10964 if (fseek(f, 0, 0) == -1) {
10965 /* f is not seekable; probably a pipe */
10968 if (useList && n == 0) {
10969 int error = GameListBuild(f);
10971 DisplayError(_("Cannot build game list"), error);
10972 } else if (!ListEmpty(&gameList) &&
10973 ((ListGame *) gameList.tailPred)->number > 1) {
10974 GameListPopUp(f, title);
10981 return LoadGame(f, n, title, FALSE);
10986 MakeRegisteredMove ()
10988 int fromX, fromY, toX, toY;
10990 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10991 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10994 if (appData.debugMode)
10995 fprintf(debugFP, "Restoring %s for game %d\n",
10996 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10998 thinkOutput[0] = NULLCHAR;
10999 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11000 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11001 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11002 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11003 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11004 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11005 MakeMove(fromX, fromY, toX, toY, promoChar);
11006 ShowMove(fromX, fromY, toX, toY);
11008 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11015 if (WhiteOnMove(currentMove)) {
11016 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11018 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11023 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11030 if (WhiteOnMove(currentMove)) {
11031 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11033 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11038 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11049 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11051 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11055 if (gameNumber > nCmailGames) {
11056 DisplayError(_("No more games in this message"), 0);
11059 if (f == lastLoadGameFP) {
11060 int offset = gameNumber - lastLoadGameNumber;
11062 cmailMsg[0] = NULLCHAR;
11063 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11064 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11065 nCmailMovesRegistered--;
11067 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11068 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11069 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11072 if (! RegisterMove()) return FALSE;
11076 retVal = LoadGame(f, gameNumber, title, useList);
11078 /* Make move registered during previous look at this game, if any */
11079 MakeRegisteredMove();
11081 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11082 commentList[currentMove]
11083 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11084 DisplayComment(currentMove - 1, commentList[currentMove]);
11090 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11092 ReloadGame (int offset)
11094 int gameNumber = lastLoadGameNumber + offset;
11095 if (lastLoadGameFP == NULL) {
11096 DisplayError(_("No game has been loaded yet"), 0);
11099 if (gameNumber <= 0) {
11100 DisplayError(_("Can't back up any further"), 0);
11103 if (cmailMsgLoaded) {
11104 return CmailLoadGame(lastLoadGameFP, gameNumber,
11105 lastLoadGameTitle, lastLoadGameUseList);
11107 return LoadGame(lastLoadGameFP, gameNumber,
11108 lastLoadGameTitle, lastLoadGameUseList);
11112 int keys[EmptySquare+1];
11115 PositionMatches (Board b1, Board b2)
11118 switch(appData.searchMode) {
11119 case 1: return CompareWithRights(b1, b2);
11121 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11122 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11126 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11127 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11128 sum += keys[b1[r][f]] - keys[b2[r][f]];
11132 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11133 sum += keys[b1[r][f]] - keys[b2[r][f]];
11145 int pieceList[256], quickBoard[256];
11146 ChessSquare pieceType[256] = { EmptySquare };
11147 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11148 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11149 int soughtTotal, turn;
11150 Boolean epOK, flipSearch;
11153 unsigned char piece, to;
11156 #define DSIZE (250000)
11158 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11159 Move *moveDatabase = initialSpace;
11160 unsigned int movePtr, dataSize = DSIZE;
11163 MakePieceList (Board board, int *counts)
11165 int r, f, n=Q_PROMO, total=0;
11166 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11167 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11168 int sq = f + (r<<4);
11169 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11170 quickBoard[sq] = ++n;
11172 pieceType[n] = board[r][f];
11173 counts[board[r][f]]++;
11174 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11175 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11179 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11184 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11186 int sq = fromX + (fromY<<4);
11187 int piece = quickBoard[sq];
11188 quickBoard[sq] = 0;
11189 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11190 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11191 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11192 moveDatabase[movePtr++].piece = Q_WCASTL;
11193 quickBoard[sq] = piece;
11194 piece = quickBoard[from]; quickBoard[from] = 0;
11195 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11197 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11198 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11199 moveDatabase[movePtr++].piece = Q_BCASTL;
11200 quickBoard[sq] = piece;
11201 piece = quickBoard[from]; quickBoard[from] = 0;
11202 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11204 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11205 quickBoard[(fromY<<4)+toX] = 0;
11206 moveDatabase[movePtr].piece = Q_EP;
11207 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11208 moveDatabase[movePtr].to = sq;
11210 if(promoPiece != pieceType[piece]) {
11211 moveDatabase[movePtr++].piece = Q_PROMO;
11212 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11214 moveDatabase[movePtr].piece = piece;
11215 quickBoard[sq] = piece;
11220 PackGame (Board board)
11222 Move *newSpace = NULL;
11223 moveDatabase[movePtr].piece = 0; // terminate previous game
11224 if(movePtr > dataSize) {
11225 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11226 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11227 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11230 Move *p = moveDatabase, *q = newSpace;
11231 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11232 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11233 moveDatabase = newSpace;
11234 } else { // calloc failed, we must be out of memory. Too bad...
11235 dataSize = 0; // prevent calloc events for all subsequent games
11236 return 0; // and signal this one isn't cached
11240 MakePieceList(board, counts);
11245 QuickCompare (Board board, int *minCounts, int *maxCounts)
11246 { // compare according to search mode
11248 switch(appData.searchMode)
11250 case 1: // exact position match
11251 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11252 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11253 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11256 case 2: // can have extra material on empty squares
11257 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11258 if(board[r][f] == EmptySquare) continue;
11259 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11262 case 3: // material with exact Pawn structure
11263 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11264 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11265 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11266 } // fall through to material comparison
11267 case 4: // exact material
11268 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11270 case 6: // material range with given imbalance
11271 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11272 // fall through to range comparison
11273 case 5: // material range
11274 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11280 QuickScan (Board board, Move *move)
11281 { // reconstruct game,and compare all positions in it
11282 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11284 int piece = move->piece;
11285 int to = move->to, from = pieceList[piece];
11286 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11287 if(!piece) return -1;
11288 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11289 piece = (++move)->piece;
11290 from = pieceList[piece];
11291 counts[pieceType[piece]]--;
11292 pieceType[piece] = (ChessSquare) move->to;
11293 counts[move->to]++;
11294 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11295 counts[pieceType[quickBoard[to]]]--;
11296 quickBoard[to] = 0; total--;
11299 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11300 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11301 from = pieceList[piece]; // so this must be King
11302 quickBoard[from] = 0;
11303 quickBoard[to] = piece;
11304 pieceList[piece] = to;
11309 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11310 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11311 quickBoard[from] = 0;
11312 quickBoard[to] = piece;
11313 pieceList[piece] = to;
11315 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11316 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11317 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11318 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11320 static int lastCounts[EmptySquare+1];
11322 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11323 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11324 } else stretch = 0;
11325 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11334 flipSearch = FALSE;
11335 CopyBoard(soughtBoard, boards[currentMove]);
11336 soughtTotal = MakePieceList(soughtBoard, maxSought);
11337 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11338 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11339 CopyBoard(reverseBoard, boards[currentMove]);
11340 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11341 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11342 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11343 reverseBoard[r][f] = piece;
11345 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11346 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11347 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11348 || (boards[currentMove][CASTLING][2] == NoRights ||
11349 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11350 && (boards[currentMove][CASTLING][5] == NoRights ||
11351 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11354 CopyBoard(flipBoard, soughtBoard);
11355 CopyBoard(rotateBoard, reverseBoard);
11356 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11357 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11358 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11361 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11362 if(appData.searchMode >= 5) {
11363 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11364 MakePieceList(soughtBoard, minSought);
11365 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11367 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11368 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11371 GameInfo dummyInfo;
11374 GameContainsPosition (FILE *f, ListGame *lg)
11376 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11377 int fromX, fromY, toX, toY;
11379 static int initDone=FALSE;
11381 // weed out games based on numerical tag comparison
11382 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11383 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11384 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11385 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11387 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11390 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11391 else CopyBoard(boards[scratch], initialPosition); // default start position
11394 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11395 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11398 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11399 fseek(f, lg->offset, 0);
11402 yyboardindex = scratch;
11403 quickFlag = plyNr+1;
11408 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11414 if(plyNr) return -1; // after we have seen moves, this is for new game
11417 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11418 case ImpossibleMove:
11419 case WhiteWins: // game ends here with these four
11422 case GameUnfinished:
11426 if(appData.testLegality) return -1;
11427 case WhiteCapturesEnPassant:
11428 case BlackCapturesEnPassant:
11429 case WhitePromotion:
11430 case BlackPromotion:
11431 case WhiteNonPromotion:
11432 case BlackNonPromotion:
11434 case WhiteKingSideCastle:
11435 case WhiteQueenSideCastle:
11436 case BlackKingSideCastle:
11437 case BlackQueenSideCastle:
11438 case WhiteKingSideCastleWild:
11439 case WhiteQueenSideCastleWild:
11440 case BlackKingSideCastleWild:
11441 case BlackQueenSideCastleWild:
11442 case WhiteHSideCastleFR:
11443 case WhiteASideCastleFR:
11444 case BlackHSideCastleFR:
11445 case BlackASideCastleFR:
11446 fromX = currentMoveString[0] - AAA;
11447 fromY = currentMoveString[1] - ONE;
11448 toX = currentMoveString[2] - AAA;
11449 toY = currentMoveString[3] - ONE;
11450 promoChar = currentMoveString[4];
11454 fromX = next == WhiteDrop ?
11455 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11456 (int) CharToPiece(ToLower(currentMoveString[0]));
11458 toX = currentMoveString[2] - AAA;
11459 toY = currentMoveString[3] - ONE;
11463 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11465 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11466 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11467 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11468 if(appData.findMirror) {
11469 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11470 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11475 /* Load the nth game from open file f */
11477 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11481 int gn = gameNumber;
11482 ListGame *lg = NULL;
11483 int numPGNTags = 0;
11485 GameMode oldGameMode;
11486 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11488 if (appData.debugMode)
11489 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11491 if (gameMode == Training )
11492 SetTrainingModeOff();
11494 oldGameMode = gameMode;
11495 if (gameMode != BeginningOfGame) {
11496 Reset(FALSE, TRUE);
11500 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11501 fclose(lastLoadGameFP);
11505 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11508 fseek(f, lg->offset, 0);
11509 GameListHighlight(gameNumber);
11510 pos = lg->position;
11514 DisplayError(_("Game number out of range"), 0);
11519 if (fseek(f, 0, 0) == -1) {
11520 if (f == lastLoadGameFP ?
11521 gameNumber == lastLoadGameNumber + 1 :
11525 DisplayError(_("Can't seek on game file"), 0);
11530 lastLoadGameFP = f;
11531 lastLoadGameNumber = gameNumber;
11532 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11533 lastLoadGameUseList = useList;
11537 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11538 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11539 lg->gameInfo.black);
11541 } else if (*title != NULLCHAR) {
11542 if (gameNumber > 1) {
11543 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11546 DisplayTitle(title);
11550 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11551 gameMode = PlayFromGameFile;
11555 currentMove = forwardMostMove = backwardMostMove = 0;
11556 CopyBoard(boards[0], initialPosition);
11560 * Skip the first gn-1 games in the file.
11561 * Also skip over anything that precedes an identifiable
11562 * start of game marker, to avoid being confused by
11563 * garbage at the start of the file. Currently
11564 * recognized start of game markers are the move number "1",
11565 * the pattern "gnuchess .* game", the pattern
11566 * "^[#;%] [^ ]* game file", and a PGN tag block.
11567 * A game that starts with one of the latter two patterns
11568 * will also have a move number 1, possibly
11569 * following a position diagram.
11570 * 5-4-02: Let's try being more lenient and allowing a game to
11571 * start with an unnumbered move. Does that break anything?
11573 cm = lastLoadGameStart = EndOfFile;
11575 yyboardindex = forwardMostMove;
11576 cm = (ChessMove) Myylex();
11579 if (cmailMsgLoaded) {
11580 nCmailGames = CMAIL_MAX_GAMES - gn;
11583 DisplayError(_("Game not found in file"), 0);
11590 lastLoadGameStart = cm;
11593 case MoveNumberOne:
11594 switch (lastLoadGameStart) {
11599 case MoveNumberOne:
11601 gn--; /* count this game */
11602 lastLoadGameStart = cm;
11611 switch (lastLoadGameStart) {
11614 case MoveNumberOne:
11616 gn--; /* count this game */
11617 lastLoadGameStart = cm;
11620 lastLoadGameStart = cm; /* game counted already */
11628 yyboardindex = forwardMostMove;
11629 cm = (ChessMove) Myylex();
11630 } while (cm == PGNTag || cm == Comment);
11637 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11638 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11639 != CMAIL_OLD_RESULT) {
11641 cmailResult[ CMAIL_MAX_GAMES
11642 - gn - 1] = CMAIL_OLD_RESULT;
11648 /* Only a NormalMove can be at the start of a game
11649 * without a position diagram. */
11650 if (lastLoadGameStart == EndOfFile ) {
11652 lastLoadGameStart = MoveNumberOne;
11661 if (appData.debugMode)
11662 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11664 if (cm == XBoardGame) {
11665 /* Skip any header junk before position diagram and/or move 1 */
11667 yyboardindex = forwardMostMove;
11668 cm = (ChessMove) Myylex();
11670 if (cm == EndOfFile ||
11671 cm == GNUChessGame || cm == XBoardGame) {
11672 /* Empty game; pretend end-of-file and handle later */
11677 if (cm == MoveNumberOne || cm == PositionDiagram ||
11678 cm == PGNTag || cm == Comment)
11681 } else if (cm == GNUChessGame) {
11682 if (gameInfo.event != NULL) {
11683 free(gameInfo.event);
11685 gameInfo.event = StrSave(yy_text);
11688 startedFromSetupPosition = FALSE;
11689 while (cm == PGNTag) {
11690 if (appData.debugMode)
11691 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11692 err = ParsePGNTag(yy_text, &gameInfo);
11693 if (!err) numPGNTags++;
11695 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11696 if(gameInfo.variant != oldVariant) {
11697 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11698 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11699 InitPosition(TRUE);
11700 oldVariant = gameInfo.variant;
11701 if (appData.debugMode)
11702 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11706 if (gameInfo.fen != NULL) {
11707 Board initial_position;
11708 startedFromSetupPosition = TRUE;
11709 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11711 DisplayError(_("Bad FEN position in file"), 0);
11714 CopyBoard(boards[0], initial_position);
11715 if (blackPlaysFirst) {
11716 currentMove = forwardMostMove = backwardMostMove = 1;
11717 CopyBoard(boards[1], initial_position);
11718 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11719 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11720 timeRemaining[0][1] = whiteTimeRemaining;
11721 timeRemaining[1][1] = blackTimeRemaining;
11722 if (commentList[0] != NULL) {
11723 commentList[1] = commentList[0];
11724 commentList[0] = NULL;
11727 currentMove = forwardMostMove = backwardMostMove = 0;
11729 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11731 initialRulePlies = FENrulePlies;
11732 for( i=0; i< nrCastlingRights; i++ )
11733 initialRights[i] = initial_position[CASTLING][i];
11735 yyboardindex = forwardMostMove;
11736 free(gameInfo.fen);
11737 gameInfo.fen = NULL;
11740 yyboardindex = forwardMostMove;
11741 cm = (ChessMove) Myylex();
11743 /* Handle comments interspersed among the tags */
11744 while (cm == Comment) {
11746 if (appData.debugMode)
11747 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11749 AppendComment(currentMove, p, FALSE);
11750 yyboardindex = forwardMostMove;
11751 cm = (ChessMove) Myylex();
11755 /* don't rely on existence of Event tag since if game was
11756 * pasted from clipboard the Event tag may not exist
11758 if (numPGNTags > 0){
11760 if (gameInfo.variant == VariantNormal) {
11761 VariantClass v = StringToVariant(gameInfo.event);
11762 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11763 if(v < VariantShogi) gameInfo.variant = v;
11766 if( appData.autoDisplayTags ) {
11767 tags = PGNTags(&gameInfo);
11768 TagsPopUp(tags, CmailMsg());
11773 /* Make something up, but don't display it now */
11778 if (cm == PositionDiagram) {
11781 Board initial_position;
11783 if (appData.debugMode)
11784 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11786 if (!startedFromSetupPosition) {
11788 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11789 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11800 initial_position[i][j++] = CharToPiece(*p);
11803 while (*p == ' ' || *p == '\t' ||
11804 *p == '\n' || *p == '\r') p++;
11806 if (strncmp(p, "black", strlen("black"))==0)
11807 blackPlaysFirst = TRUE;
11809 blackPlaysFirst = FALSE;
11810 startedFromSetupPosition = TRUE;
11812 CopyBoard(boards[0], initial_position);
11813 if (blackPlaysFirst) {
11814 currentMove = forwardMostMove = backwardMostMove = 1;
11815 CopyBoard(boards[1], initial_position);
11816 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11817 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11818 timeRemaining[0][1] = whiteTimeRemaining;
11819 timeRemaining[1][1] = blackTimeRemaining;
11820 if (commentList[0] != NULL) {
11821 commentList[1] = commentList[0];
11822 commentList[0] = NULL;
11825 currentMove = forwardMostMove = backwardMostMove = 0;
11828 yyboardindex = forwardMostMove;
11829 cm = (ChessMove) Myylex();
11832 if (first.pr == NoProc) {
11833 StartChessProgram(&first);
11835 InitChessProgram(&first, FALSE);
11836 SendToProgram("force\n", &first);
11837 if (startedFromSetupPosition) {
11838 SendBoard(&first, forwardMostMove);
11839 if (appData.debugMode) {
11840 fprintf(debugFP, "Load Game\n");
11842 DisplayBothClocks();
11845 /* [HGM] server: flag to write setup moves in broadcast file as one */
11846 loadFlag = appData.suppressLoadMoves;
11848 while (cm == Comment) {
11850 if (appData.debugMode)
11851 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11853 AppendComment(currentMove, p, FALSE);
11854 yyboardindex = forwardMostMove;
11855 cm = (ChessMove) Myylex();
11858 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11859 cm == WhiteWins || cm == BlackWins ||
11860 cm == GameIsDrawn || cm == GameUnfinished) {
11861 DisplayMessage("", _("No moves in game"));
11862 if (cmailMsgLoaded) {
11863 if (appData.debugMode)
11864 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11868 DrawPosition(FALSE, boards[currentMove]);
11869 DisplayBothClocks();
11870 gameMode = EditGame;
11877 // [HGM] PV info: routine tests if comment empty
11878 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11879 DisplayComment(currentMove - 1, commentList[currentMove]);
11881 if (!matchMode && appData.timeDelay != 0)
11882 DrawPosition(FALSE, boards[currentMove]);
11884 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11885 programStats.ok_to_send = 1;
11888 /* if the first token after the PGN tags is a move
11889 * and not move number 1, retrieve it from the parser
11891 if (cm != MoveNumberOne)
11892 LoadGameOneMove(cm);
11894 /* load the remaining moves from the file */
11895 while (LoadGameOneMove(EndOfFile)) {
11896 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11897 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11900 /* rewind to the start of the game */
11901 currentMove = backwardMostMove;
11903 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11905 if (oldGameMode == AnalyzeFile ||
11906 oldGameMode == AnalyzeMode) {
11907 AnalyzeFileEvent();
11910 if (!matchMode && pos >= 0) {
11911 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11913 if (matchMode || appData.timeDelay == 0) {
11915 } else if (appData.timeDelay > 0) {
11916 AutoPlayGameLoop();
11919 if (appData.debugMode)
11920 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11922 loadFlag = 0; /* [HGM] true game starts */
11926 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11928 ReloadPosition (int offset)
11930 int positionNumber = lastLoadPositionNumber + offset;
11931 if (lastLoadPositionFP == NULL) {
11932 DisplayError(_("No position has been loaded yet"), 0);
11935 if (positionNumber <= 0) {
11936 DisplayError(_("Can't back up any further"), 0);
11939 return LoadPosition(lastLoadPositionFP, positionNumber,
11940 lastLoadPositionTitle);
11943 /* Load the nth position from the given file */
11945 LoadPositionFromFile (char *filename, int n, char *title)
11950 if (strcmp(filename, "-") == 0) {
11951 return LoadPosition(stdin, n, "stdin");
11953 f = fopen(filename, "rb");
11955 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11956 DisplayError(buf, errno);
11959 return LoadPosition(f, n, title);
11964 /* Load the nth position from the given open file, and close it */
11966 LoadPosition (FILE *f, int positionNumber, char *title)
11968 char *p, line[MSG_SIZ];
11969 Board initial_position;
11970 int i, j, fenMode, pn;
11972 if (gameMode == Training )
11973 SetTrainingModeOff();
11975 if (gameMode != BeginningOfGame) {
11976 Reset(FALSE, TRUE);
11978 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11979 fclose(lastLoadPositionFP);
11981 if (positionNumber == 0) positionNumber = 1;
11982 lastLoadPositionFP = f;
11983 lastLoadPositionNumber = positionNumber;
11984 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11985 if (first.pr == NoProc && !appData.noChessProgram) {
11986 StartChessProgram(&first);
11987 InitChessProgram(&first, FALSE);
11989 pn = positionNumber;
11990 if (positionNumber < 0) {
11991 /* Negative position number means to seek to that byte offset */
11992 if (fseek(f, -positionNumber, 0) == -1) {
11993 DisplayError(_("Can't seek on position file"), 0);
11998 if (fseek(f, 0, 0) == -1) {
11999 if (f == lastLoadPositionFP ?
12000 positionNumber == lastLoadPositionNumber + 1 :
12001 positionNumber == 1) {
12004 DisplayError(_("Can't seek on position file"), 0);
12009 /* See if this file is FEN or old-style xboard */
12010 if (fgets(line, MSG_SIZ, f) == NULL) {
12011 DisplayError(_("Position not found in file"), 0);
12014 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12015 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12018 if (fenMode || line[0] == '#') pn--;
12020 /* skip positions before number pn */
12021 if (fgets(line, MSG_SIZ, f) == NULL) {
12023 DisplayError(_("Position not found in file"), 0);
12026 if (fenMode || line[0] == '#') pn--;
12031 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12032 DisplayError(_("Bad FEN position in file"), 0);
12036 (void) fgets(line, MSG_SIZ, f);
12037 (void) fgets(line, MSG_SIZ, f);
12039 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12040 (void) fgets(line, MSG_SIZ, f);
12041 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12044 initial_position[i][j++] = CharToPiece(*p);
12048 blackPlaysFirst = FALSE;
12050 (void) fgets(line, MSG_SIZ, f);
12051 if (strncmp(line, "black", strlen("black"))==0)
12052 blackPlaysFirst = TRUE;
12055 startedFromSetupPosition = TRUE;
12057 CopyBoard(boards[0], initial_position);
12058 if (blackPlaysFirst) {
12059 currentMove = forwardMostMove = backwardMostMove = 1;
12060 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12061 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12062 CopyBoard(boards[1], initial_position);
12063 DisplayMessage("", _("Black to play"));
12065 currentMove = forwardMostMove = backwardMostMove = 0;
12066 DisplayMessage("", _("White to play"));
12068 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12069 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12070 SendToProgram("force\n", &first);
12071 SendBoard(&first, forwardMostMove);
12073 if (appData.debugMode) {
12075 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12076 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12077 fprintf(debugFP, "Load Position\n");
12080 if (positionNumber > 1) {
12081 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12082 DisplayTitle(line);
12084 DisplayTitle(title);
12086 gameMode = EditGame;
12089 timeRemaining[0][1] = whiteTimeRemaining;
12090 timeRemaining[1][1] = blackTimeRemaining;
12091 DrawPosition(FALSE, boards[currentMove]);
12098 CopyPlayerNameIntoFileName (char **dest, char *src)
12100 while (*src != NULLCHAR && *src != ',') {
12105 *(*dest)++ = *src++;
12111 DefaultFileName (char *ext)
12113 static char def[MSG_SIZ];
12116 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12118 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12120 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12122 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12129 /* Save the current game to the given file */
12131 SaveGameToFile (char *filename, int append)
12135 int result, i, t,tot=0;
12137 if (strcmp(filename, "-") == 0) {
12138 return SaveGame(stdout, 0, NULL);
12140 for(i=0; i<10; i++) { // upto 10 tries
12141 f = fopen(filename, append ? "a" : "w");
12142 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12143 if(f || errno != 13) break;
12144 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12148 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12149 DisplayError(buf, errno);
12152 safeStrCpy(buf, lastMsg, MSG_SIZ);
12153 DisplayMessage(_("Waiting for access to save file"), "");
12154 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12155 DisplayMessage(_("Saving game"), "");
12156 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12157 result = SaveGame(f, 0, NULL);
12158 DisplayMessage(buf, "");
12165 SavePart (char *str)
12167 static char buf[MSG_SIZ];
12170 p = strchr(str, ' ');
12171 if (p == NULL) return str;
12172 strncpy(buf, str, p - str);
12173 buf[p - str] = NULLCHAR;
12177 #define PGN_MAX_LINE 75
12179 #define PGN_SIDE_WHITE 0
12180 #define PGN_SIDE_BLACK 1
12183 FindFirstMoveOutOfBook (int side)
12187 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12188 int index = backwardMostMove;
12189 int has_book_hit = 0;
12191 if( (index % 2) != side ) {
12195 while( index < forwardMostMove ) {
12196 /* Check to see if engine is in book */
12197 int depth = pvInfoList[index].depth;
12198 int score = pvInfoList[index].score;
12204 else if( score == 0 && depth == 63 ) {
12205 in_book = 1; /* Zappa */
12207 else if( score == 2 && depth == 99 ) {
12208 in_book = 1; /* Abrok */
12211 has_book_hit += in_book;
12227 GetOutOfBookInfo (char * buf)
12231 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12233 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12234 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12238 if( oob[0] >= 0 || oob[1] >= 0 ) {
12239 for( i=0; i<2; i++ ) {
12243 if( i > 0 && oob[0] >= 0 ) {
12244 strcat( buf, " " );
12247 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12248 sprintf( buf+strlen(buf), "%s%.2f",
12249 pvInfoList[idx].score >= 0 ? "+" : "",
12250 pvInfoList[idx].score / 100.0 );
12256 /* Save game in PGN style and close the file */
12258 SaveGamePGN (FILE *f)
12260 int i, offset, linelen, newblock;
12264 int movelen, numlen, blank;
12265 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12267 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12269 tm = time((time_t *) NULL);
12271 PrintPGNTags(f, &gameInfo);
12273 if (backwardMostMove > 0 || startedFromSetupPosition) {
12274 char *fen = PositionToFEN(backwardMostMove, NULL);
12275 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12276 fprintf(f, "\n{--------------\n");
12277 PrintPosition(f, backwardMostMove);
12278 fprintf(f, "--------------}\n");
12282 /* [AS] Out of book annotation */
12283 if( appData.saveOutOfBookInfo ) {
12286 GetOutOfBookInfo( buf );
12288 if( buf[0] != '\0' ) {
12289 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12296 i = backwardMostMove;
12300 while (i < forwardMostMove) {
12301 /* Print comments preceding this move */
12302 if (commentList[i] != NULL) {
12303 if (linelen > 0) fprintf(f, "\n");
12304 fprintf(f, "%s", commentList[i]);
12309 /* Format move number */
12311 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12314 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12316 numtext[0] = NULLCHAR;
12318 numlen = strlen(numtext);
12321 /* Print move number */
12322 blank = linelen > 0 && numlen > 0;
12323 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12332 fprintf(f, "%s", numtext);
12336 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12337 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12340 blank = linelen > 0 && movelen > 0;
12341 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12350 fprintf(f, "%s", move_buffer);
12351 linelen += movelen;
12353 /* [AS] Add PV info if present */
12354 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12355 /* [HGM] add time */
12356 char buf[MSG_SIZ]; int seconds;
12358 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12364 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12367 seconds = (seconds + 4)/10; // round to full seconds
12369 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12371 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12374 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12375 pvInfoList[i].score >= 0 ? "+" : "",
12376 pvInfoList[i].score / 100.0,
12377 pvInfoList[i].depth,
12380 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12382 /* Print score/depth */
12383 blank = linelen > 0 && movelen > 0;
12384 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12393 fprintf(f, "%s", move_buffer);
12394 linelen += movelen;
12400 /* Start a new line */
12401 if (linelen > 0) fprintf(f, "\n");
12403 /* Print comments after last move */
12404 if (commentList[i] != NULL) {
12405 fprintf(f, "%s\n", commentList[i]);
12409 if (gameInfo.resultDetails != NULL &&
12410 gameInfo.resultDetails[0] != NULLCHAR) {
12411 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12412 PGNResult(gameInfo.result));
12414 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12418 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12422 /* Save game in old style and close the file */
12424 SaveGameOldStyle (FILE *f)
12429 tm = time((time_t *) NULL);
12431 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12434 if (backwardMostMove > 0 || startedFromSetupPosition) {
12435 fprintf(f, "\n[--------------\n");
12436 PrintPosition(f, backwardMostMove);
12437 fprintf(f, "--------------]\n");
12442 i = backwardMostMove;
12443 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12445 while (i < forwardMostMove) {
12446 if (commentList[i] != NULL) {
12447 fprintf(f, "[%s]\n", commentList[i]);
12450 if ((i % 2) == 1) {
12451 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12454 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12456 if (commentList[i] != NULL) {
12460 if (i >= forwardMostMove) {
12464 fprintf(f, "%s\n", parseList[i]);
12469 if (commentList[i] != NULL) {
12470 fprintf(f, "[%s]\n", commentList[i]);
12473 /* This isn't really the old style, but it's close enough */
12474 if (gameInfo.resultDetails != NULL &&
12475 gameInfo.resultDetails[0] != NULLCHAR) {
12476 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12477 gameInfo.resultDetails);
12479 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12486 /* Save the current game to open file f and close the file */
12488 SaveGame (FILE *f, int dummy, char *dummy2)
12490 if (gameMode == EditPosition) EditPositionDone(TRUE);
12491 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12492 if (appData.oldSaveStyle)
12493 return SaveGameOldStyle(f);
12495 return SaveGamePGN(f);
12498 /* Save the current position to the given file */
12500 SavePositionToFile (char *filename)
12505 if (strcmp(filename, "-") == 0) {
12506 return SavePosition(stdout, 0, NULL);
12508 f = fopen(filename, "a");
12510 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12511 DisplayError(buf, errno);
12514 safeStrCpy(buf, lastMsg, MSG_SIZ);
12515 DisplayMessage(_("Waiting for access to save file"), "");
12516 flock(fileno(f), LOCK_EX); // [HGM] lock
12517 DisplayMessage(_("Saving position"), "");
12518 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12519 SavePosition(f, 0, NULL);
12520 DisplayMessage(buf, "");
12526 /* Save the current position to the given open file and close the file */
12528 SavePosition (FILE *f, int dummy, char *dummy2)
12533 if (gameMode == EditPosition) EditPositionDone(TRUE);
12534 if (appData.oldSaveStyle) {
12535 tm = time((time_t *) NULL);
12537 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12539 fprintf(f, "[--------------\n");
12540 PrintPosition(f, currentMove);
12541 fprintf(f, "--------------]\n");
12543 fen = PositionToFEN(currentMove, NULL);
12544 fprintf(f, "%s\n", fen);
12552 ReloadCmailMsgEvent (int unregister)
12555 static char *inFilename = NULL;
12556 static char *outFilename;
12558 struct stat inbuf, outbuf;
12561 /* Any registered moves are unregistered if unregister is set, */
12562 /* i.e. invoked by the signal handler */
12564 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12565 cmailMoveRegistered[i] = FALSE;
12566 if (cmailCommentList[i] != NULL) {
12567 free(cmailCommentList[i]);
12568 cmailCommentList[i] = NULL;
12571 nCmailMovesRegistered = 0;
12574 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12575 cmailResult[i] = CMAIL_NOT_RESULT;
12579 if (inFilename == NULL) {
12580 /* Because the filenames are static they only get malloced once */
12581 /* and they never get freed */
12582 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12583 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12585 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12586 sprintf(outFilename, "%s.out", appData.cmailGameName);
12589 status = stat(outFilename, &outbuf);
12591 cmailMailedMove = FALSE;
12593 status = stat(inFilename, &inbuf);
12594 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12597 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12598 counts the games, notes how each one terminated, etc.
12600 It would be nice to remove this kludge and instead gather all
12601 the information while building the game list. (And to keep it
12602 in the game list nodes instead of having a bunch of fixed-size
12603 parallel arrays.) Note this will require getting each game's
12604 termination from the PGN tags, as the game list builder does
12605 not process the game moves. --mann
12607 cmailMsgLoaded = TRUE;
12608 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12610 /* Load first game in the file or popup game menu */
12611 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12613 #endif /* !WIN32 */
12621 char string[MSG_SIZ];
12623 if ( cmailMailedMove
12624 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12625 return TRUE; /* Allow free viewing */
12628 /* Unregister move to ensure that we don't leave RegisterMove */
12629 /* with the move registered when the conditions for registering no */
12631 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12632 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12633 nCmailMovesRegistered --;
12635 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12637 free(cmailCommentList[lastLoadGameNumber - 1]);
12638 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12642 if (cmailOldMove == -1) {
12643 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12647 if (currentMove > cmailOldMove + 1) {
12648 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12652 if (currentMove < cmailOldMove) {
12653 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12657 if (forwardMostMove > currentMove) {
12658 /* Silently truncate extra moves */
12662 if ( (currentMove == cmailOldMove + 1)
12663 || ( (currentMove == cmailOldMove)
12664 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12665 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12666 if (gameInfo.result != GameUnfinished) {
12667 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12670 if (commentList[currentMove] != NULL) {
12671 cmailCommentList[lastLoadGameNumber - 1]
12672 = StrSave(commentList[currentMove]);
12674 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12676 if (appData.debugMode)
12677 fprintf(debugFP, "Saving %s for game %d\n",
12678 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12680 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12682 f = fopen(string, "w");
12683 if (appData.oldSaveStyle) {
12684 SaveGameOldStyle(f); /* also closes the file */
12686 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12687 f = fopen(string, "w");
12688 SavePosition(f, 0, NULL); /* also closes the file */
12690 fprintf(f, "{--------------\n");
12691 PrintPosition(f, currentMove);
12692 fprintf(f, "--------------}\n\n");
12694 SaveGame(f, 0, NULL); /* also closes the file*/
12697 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12698 nCmailMovesRegistered ++;
12699 } else if (nCmailGames == 1) {
12700 DisplayError(_("You have not made a move yet"), 0);
12711 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12712 FILE *commandOutput;
12713 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12714 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12720 if (! cmailMsgLoaded) {
12721 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12725 if (nCmailGames == nCmailResults) {
12726 DisplayError(_("No unfinished games"), 0);
12730 #if CMAIL_PROHIBIT_REMAIL
12731 if (cmailMailedMove) {
12732 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);
12733 DisplayError(msg, 0);
12738 if (! (cmailMailedMove || RegisterMove())) return;
12740 if ( cmailMailedMove
12741 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12742 snprintf(string, MSG_SIZ, partCommandString,
12743 appData.debugMode ? " -v" : "", appData.cmailGameName);
12744 commandOutput = popen(string, "r");
12746 if (commandOutput == NULL) {
12747 DisplayError(_("Failed to invoke cmail"), 0);
12749 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12750 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12752 if (nBuffers > 1) {
12753 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12754 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12755 nBytes = MSG_SIZ - 1;
12757 (void) memcpy(msg, buffer, nBytes);
12759 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12761 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12762 cmailMailedMove = TRUE; /* Prevent >1 moves */
12765 for (i = 0; i < nCmailGames; i ++) {
12766 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12771 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12773 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12775 appData.cmailGameName,
12777 LoadGameFromFile(buffer, 1, buffer, FALSE);
12778 cmailMsgLoaded = FALSE;
12782 DisplayInformation(msg);
12783 pclose(commandOutput);
12786 if ((*cmailMsg) != '\0') {
12787 DisplayInformation(cmailMsg);
12792 #endif /* !WIN32 */
12801 int prependComma = 0;
12803 char string[MSG_SIZ]; /* Space for game-list */
12806 if (!cmailMsgLoaded) return "";
12808 if (cmailMailedMove) {
12809 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12811 /* Create a list of games left */
12812 snprintf(string, MSG_SIZ, "[");
12813 for (i = 0; i < nCmailGames; i ++) {
12814 if (! ( cmailMoveRegistered[i]
12815 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12816 if (prependComma) {
12817 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12819 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12823 strcat(string, number);
12826 strcat(string, "]");
12828 if (nCmailMovesRegistered + nCmailResults == 0) {
12829 switch (nCmailGames) {
12831 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12835 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12839 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12844 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12846 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12851 if (nCmailResults == nCmailGames) {
12852 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12854 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12859 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12871 if (gameMode == Training)
12872 SetTrainingModeOff();
12875 cmailMsgLoaded = FALSE;
12876 if (appData.icsActive) {
12877 SendToICS(ics_prefix);
12878 SendToICS("refresh\n");
12883 ExitEvent (int status)
12887 /* Give up on clean exit */
12891 /* Keep trying for clean exit */
12895 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12897 if (telnetISR != NULL) {
12898 RemoveInputSource(telnetISR);
12900 if (icsPR != NoProc) {
12901 DestroyChildProcess(icsPR, TRUE);
12904 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12905 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12907 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12908 /* make sure this other one finishes before killing it! */
12909 if(endingGame) { int count = 0;
12910 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12911 while(endingGame && count++ < 10) DoSleep(1);
12912 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12915 /* Kill off chess programs */
12916 if (first.pr != NoProc) {
12919 DoSleep( appData.delayBeforeQuit );
12920 SendToProgram("quit\n", &first);
12921 DoSleep( appData.delayAfterQuit );
12922 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12924 if (second.pr != NoProc) {
12925 DoSleep( appData.delayBeforeQuit );
12926 SendToProgram("quit\n", &second);
12927 DoSleep( appData.delayAfterQuit );
12928 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12930 if (first.isr != NULL) {
12931 RemoveInputSource(first.isr);
12933 if (second.isr != NULL) {
12934 RemoveInputSource(second.isr);
12937 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12938 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12940 ShutDownFrontEnd();
12947 if (appData.debugMode)
12948 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12952 if (gameMode == MachinePlaysWhite ||
12953 gameMode == MachinePlaysBlack) {
12956 DisplayBothClocks();
12958 if (gameMode == PlayFromGameFile) {
12959 if (appData.timeDelay >= 0)
12960 AutoPlayGameLoop();
12961 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12962 Reset(FALSE, TRUE);
12963 SendToICS(ics_prefix);
12964 SendToICS("refresh\n");
12965 } else if (currentMove < forwardMostMove) {
12966 ForwardInner(forwardMostMove);
12968 pauseExamInvalid = FALSE;
12970 switch (gameMode) {
12974 pauseExamForwardMostMove = forwardMostMove;
12975 pauseExamInvalid = FALSE;
12978 case IcsPlayingWhite:
12979 case IcsPlayingBlack:
12983 case PlayFromGameFile:
12984 (void) StopLoadGameTimer();
12988 case BeginningOfGame:
12989 if (appData.icsActive) return;
12990 /* else fall through */
12991 case MachinePlaysWhite:
12992 case MachinePlaysBlack:
12993 case TwoMachinesPlay:
12994 if (forwardMostMove == 0)
12995 return; /* don't pause if no one has moved */
12996 if ((gameMode == MachinePlaysWhite &&
12997 !WhiteOnMove(forwardMostMove)) ||
12998 (gameMode == MachinePlaysBlack &&
12999 WhiteOnMove(forwardMostMove))) {
13010 EditCommentEvent ()
13012 char title[MSG_SIZ];
13014 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13015 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13017 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13018 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13019 parseList[currentMove - 1]);
13022 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13029 char *tags = PGNTags(&gameInfo);
13031 EditTagsPopUp(tags, NULL);
13036 AnalyzeModeEvent ()
13038 if (appData.noChessProgram || gameMode == AnalyzeMode)
13041 if (gameMode != AnalyzeFile) {
13042 if (!appData.icsEngineAnalyze) {
13044 if (gameMode != EditGame) return;
13046 ResurrectChessProgram();
13047 SendToProgram("analyze\n", &first);
13048 first.analyzing = TRUE;
13049 /*first.maybeThinking = TRUE;*/
13050 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13051 EngineOutputPopUp();
13053 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13058 StartAnalysisClock();
13059 GetTimeMark(&lastNodeCountTime);
13064 AnalyzeFileEvent ()
13066 if (appData.noChessProgram || gameMode == AnalyzeFile)
13069 if (gameMode != AnalyzeMode) {
13071 if (gameMode != EditGame) return;
13072 ResurrectChessProgram();
13073 SendToProgram("analyze\n", &first);
13074 first.analyzing = TRUE;
13075 /*first.maybeThinking = TRUE;*/
13076 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13077 EngineOutputPopUp();
13079 gameMode = AnalyzeFile;
13084 StartAnalysisClock();
13085 GetTimeMark(&lastNodeCountTime);
13087 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13091 MachineWhiteEvent ()
13094 char *bookHit = NULL;
13096 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13100 if (gameMode == PlayFromGameFile ||
13101 gameMode == TwoMachinesPlay ||
13102 gameMode == Training ||
13103 gameMode == AnalyzeMode ||
13104 gameMode == EndOfGame)
13107 if (gameMode == EditPosition)
13108 EditPositionDone(TRUE);
13110 if (!WhiteOnMove(currentMove)) {
13111 DisplayError(_("It is not White's turn"), 0);
13115 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13118 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13119 gameMode == AnalyzeFile)
13122 ResurrectChessProgram(); /* in case it isn't running */
13123 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13124 gameMode = MachinePlaysWhite;
13127 gameMode = MachinePlaysWhite;
13131 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13133 if (first.sendName) {
13134 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13135 SendToProgram(buf, &first);
13137 if (first.sendTime) {
13138 if (first.useColors) {
13139 SendToProgram("black\n", &first); /*gnu kludge*/
13141 SendTimeRemaining(&first, TRUE);
13143 if (first.useColors) {
13144 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13146 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13147 SetMachineThinkingEnables();
13148 first.maybeThinking = TRUE;
13152 if (appData.autoFlipView && !flipView) {
13153 flipView = !flipView;
13154 DrawPosition(FALSE, NULL);
13155 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13158 if(bookHit) { // [HGM] book: simulate book reply
13159 static char bookMove[MSG_SIZ]; // a bit generous?
13161 programStats.nodes = programStats.depth = programStats.time =
13162 programStats.score = programStats.got_only_move = 0;
13163 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13165 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13166 strcat(bookMove, bookHit);
13167 HandleMachineMove(bookMove, &first);
13172 MachineBlackEvent ()
13175 char *bookHit = NULL;
13177 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13181 if (gameMode == PlayFromGameFile ||
13182 gameMode == TwoMachinesPlay ||
13183 gameMode == Training ||
13184 gameMode == AnalyzeMode ||
13185 gameMode == EndOfGame)
13188 if (gameMode == EditPosition)
13189 EditPositionDone(TRUE);
13191 if (WhiteOnMove(currentMove)) {
13192 DisplayError(_("It is not Black's turn"), 0);
13196 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13199 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13200 gameMode == AnalyzeFile)
13203 ResurrectChessProgram(); /* in case it isn't running */
13204 gameMode = MachinePlaysBlack;
13208 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13210 if (first.sendName) {
13211 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13212 SendToProgram(buf, &first);
13214 if (first.sendTime) {
13215 if (first.useColors) {
13216 SendToProgram("white\n", &first); /*gnu kludge*/
13218 SendTimeRemaining(&first, FALSE);
13220 if (first.useColors) {
13221 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13223 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13224 SetMachineThinkingEnables();
13225 first.maybeThinking = TRUE;
13228 if (appData.autoFlipView && flipView) {
13229 flipView = !flipView;
13230 DrawPosition(FALSE, NULL);
13231 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13233 if(bookHit) { // [HGM] book: simulate book reply
13234 static char bookMove[MSG_SIZ]; // a bit generous?
13236 programStats.nodes = programStats.depth = programStats.time =
13237 programStats.score = programStats.got_only_move = 0;
13238 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13240 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13241 strcat(bookMove, bookHit);
13242 HandleMachineMove(bookMove, &first);
13248 DisplayTwoMachinesTitle ()
13251 if (appData.matchGames > 0) {
13252 if(appData.tourneyFile[0]) {
13253 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13254 gameInfo.white, _("vs."), gameInfo.black,
13255 nextGame+1, appData.matchGames+1,
13256 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13258 if (first.twoMachinesColor[0] == 'w') {
13259 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13260 gameInfo.white, _("vs."), gameInfo.black,
13261 first.matchWins, second.matchWins,
13262 matchGame - 1 - (first.matchWins + second.matchWins));
13264 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13265 gameInfo.white, _("vs."), gameInfo.black,
13266 second.matchWins, first.matchWins,
13267 matchGame - 1 - (first.matchWins + second.matchWins));
13270 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13276 SettingsMenuIfReady ()
13278 if (second.lastPing != second.lastPong) {
13279 DisplayMessage("", _("Waiting for second chess program"));
13280 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13284 DisplayMessage("", "");
13285 SettingsPopUp(&second);
13289 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13292 if (cps->pr == NoProc) {
13293 StartChessProgram(cps);
13294 if (cps->protocolVersion == 1) {
13297 /* kludge: allow timeout for initial "feature" command */
13299 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13300 DisplayMessage("", buf);
13301 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13309 TwoMachinesEvent P((void))
13313 ChessProgramState *onmove;
13314 char *bookHit = NULL;
13315 static int stalling = 0;
13319 if (appData.noChessProgram) return;
13321 switch (gameMode) {
13322 case TwoMachinesPlay:
13324 case MachinePlaysWhite:
13325 case MachinePlaysBlack:
13326 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13327 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13331 case BeginningOfGame:
13332 case PlayFromGameFile:
13335 if (gameMode != EditGame) return;
13338 EditPositionDone(TRUE);
13349 // forwardMostMove = currentMove;
13350 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13352 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13354 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13355 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13356 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13360 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13361 SendToProgram("force\n", &second);
13363 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13366 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13367 if(appData.matchPause>10000 || appData.matchPause<10)
13368 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13369 wait = SubtractTimeMarks(&now, &pauseStart);
13370 if(wait < appData.matchPause) {
13371 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13375 DisplayMessage("", "");
13376 if (startedFromSetupPosition) {
13377 SendBoard(&second, backwardMostMove);
13378 if (appData.debugMode) {
13379 fprintf(debugFP, "Two Machines\n");
13382 for (i = backwardMostMove; i < forwardMostMove; i++) {
13383 SendMoveToProgram(i, &second);
13386 gameMode = TwoMachinesPlay;
13388 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13390 DisplayTwoMachinesTitle();
13392 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13397 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13398 SendToProgram(first.computerString, &first);
13399 if (first.sendName) {
13400 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13401 SendToProgram(buf, &first);
13403 SendToProgram(second.computerString, &second);
13404 if (second.sendName) {
13405 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13406 SendToProgram(buf, &second);
13410 if (!first.sendTime || !second.sendTime) {
13411 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13412 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13414 if (onmove->sendTime) {
13415 if (onmove->useColors) {
13416 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13418 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13420 if (onmove->useColors) {
13421 SendToProgram(onmove->twoMachinesColor, onmove);
13423 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13424 // SendToProgram("go\n", onmove);
13425 onmove->maybeThinking = TRUE;
13426 SetMachineThinkingEnables();
13430 if(bookHit) { // [HGM] book: simulate book reply
13431 static char bookMove[MSG_SIZ]; // a bit generous?
13433 programStats.nodes = programStats.depth = programStats.time =
13434 programStats.score = programStats.got_only_move = 0;
13435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13437 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13438 strcat(bookMove, bookHit);
13439 savedMessage = bookMove; // args for deferred call
13440 savedState = onmove;
13441 ScheduleDelayedEvent(DeferredBookMove, 1);
13448 if (gameMode == Training) {
13449 SetTrainingModeOff();
13450 gameMode = PlayFromGameFile;
13451 DisplayMessage("", _("Training mode off"));
13453 gameMode = Training;
13454 animateTraining = appData.animate;
13456 /* make sure we are not already at the end of the game */
13457 if (currentMove < forwardMostMove) {
13458 SetTrainingModeOn();
13459 DisplayMessage("", _("Training mode on"));
13461 gameMode = PlayFromGameFile;
13462 DisplayError(_("Already at end of game"), 0);
13471 if (!appData.icsActive) return;
13472 switch (gameMode) {
13473 case IcsPlayingWhite:
13474 case IcsPlayingBlack:
13477 case BeginningOfGame:
13485 EditPositionDone(TRUE);
13498 gameMode = IcsIdle;
13508 switch (gameMode) {
13510 SetTrainingModeOff();
13512 case MachinePlaysWhite:
13513 case MachinePlaysBlack:
13514 case BeginningOfGame:
13515 SendToProgram("force\n", &first);
13516 SetUserThinkingEnables();
13518 case PlayFromGameFile:
13519 (void) StopLoadGameTimer();
13520 if (gameFileFP != NULL) {
13525 EditPositionDone(TRUE);
13530 SendToProgram("force\n", &first);
13532 case TwoMachinesPlay:
13533 GameEnds(EndOfFile, NULL, GE_PLAYER);
13534 ResurrectChessProgram();
13535 SetUserThinkingEnables();
13538 ResurrectChessProgram();
13540 case IcsPlayingBlack:
13541 case IcsPlayingWhite:
13542 DisplayError(_("Warning: You are still playing a game"), 0);
13545 DisplayError(_("Warning: You are still observing a game"), 0);
13548 DisplayError(_("Warning: You are still examining a game"), 0);
13559 first.offeredDraw = second.offeredDraw = 0;
13561 if (gameMode == PlayFromGameFile) {
13562 whiteTimeRemaining = timeRemaining[0][currentMove];
13563 blackTimeRemaining = timeRemaining[1][currentMove];
13567 if (gameMode == MachinePlaysWhite ||
13568 gameMode == MachinePlaysBlack ||
13569 gameMode == TwoMachinesPlay ||
13570 gameMode == EndOfGame) {
13571 i = forwardMostMove;
13572 while (i > currentMove) {
13573 SendToProgram("undo\n", &first);
13576 if(!adjustedClock) {
13577 whiteTimeRemaining = timeRemaining[0][currentMove];
13578 blackTimeRemaining = timeRemaining[1][currentMove];
13579 DisplayBothClocks();
13581 if (whiteFlag || blackFlag) {
13582 whiteFlag = blackFlag = 0;
13587 gameMode = EditGame;
13594 EditPositionEvent ()
13596 if (gameMode == EditPosition) {
13602 if (gameMode != EditGame) return;
13604 gameMode = EditPosition;
13607 if (currentMove > 0)
13608 CopyBoard(boards[0], boards[currentMove]);
13610 blackPlaysFirst = !WhiteOnMove(currentMove);
13612 currentMove = forwardMostMove = backwardMostMove = 0;
13613 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13620 /* [DM] icsEngineAnalyze - possible call from other functions */
13621 if (appData.icsEngineAnalyze) {
13622 appData.icsEngineAnalyze = FALSE;
13624 DisplayMessage("",_("Close ICS engine analyze..."));
13626 if (first.analysisSupport && first.analyzing) {
13627 SendToProgram("exit\n", &first);
13628 first.analyzing = FALSE;
13630 thinkOutput[0] = NULLCHAR;
13634 EditPositionDone (Boolean fakeRights)
13636 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13638 startedFromSetupPosition = TRUE;
13639 InitChessProgram(&first, FALSE);
13640 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13641 boards[0][EP_STATUS] = EP_NONE;
13642 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13643 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13644 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13645 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13646 } else boards[0][CASTLING][2] = NoRights;
13647 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13648 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13649 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13650 } else boards[0][CASTLING][5] = NoRights;
13652 SendToProgram("force\n", &first);
13653 if (blackPlaysFirst) {
13654 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13655 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13656 currentMove = forwardMostMove = backwardMostMove = 1;
13657 CopyBoard(boards[1], boards[0]);
13659 currentMove = forwardMostMove = backwardMostMove = 0;
13661 SendBoard(&first, forwardMostMove);
13662 if (appData.debugMode) {
13663 fprintf(debugFP, "EditPosDone\n");
13666 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13667 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13668 gameMode = EditGame;
13670 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13671 ClearHighlights(); /* [AS] */
13674 /* Pause for `ms' milliseconds */
13675 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13677 TimeDelay (long ms)
13684 } while (SubtractTimeMarks(&m2, &m1) < ms);
13687 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13689 SendMultiLineToICS (char *buf)
13691 char temp[MSG_SIZ+1], *p;
13698 strncpy(temp, buf, len);
13703 if (*p == '\n' || *p == '\r')
13708 strcat(temp, "\n");
13710 SendToPlayer(temp, strlen(temp));
13714 SetWhiteToPlayEvent ()
13716 if (gameMode == EditPosition) {
13717 blackPlaysFirst = FALSE;
13718 DisplayBothClocks(); /* works because currentMove is 0 */
13719 } else if (gameMode == IcsExamining) {
13720 SendToICS(ics_prefix);
13721 SendToICS("tomove white\n");
13726 SetBlackToPlayEvent ()
13728 if (gameMode == EditPosition) {
13729 blackPlaysFirst = TRUE;
13730 currentMove = 1; /* kludge */
13731 DisplayBothClocks();
13733 } else if (gameMode == IcsExamining) {
13734 SendToICS(ics_prefix);
13735 SendToICS("tomove black\n");
13740 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13743 ChessSquare piece = boards[0][y][x];
13745 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13747 switch (selection) {
13749 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13750 SendToICS(ics_prefix);
13751 SendToICS("bsetup clear\n");
13752 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13753 SendToICS(ics_prefix);
13754 SendToICS("clearboard\n");
13756 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13757 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13758 for (y = 0; y < BOARD_HEIGHT; y++) {
13759 if (gameMode == IcsExamining) {
13760 if (boards[currentMove][y][x] != EmptySquare) {
13761 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13766 boards[0][y][x] = p;
13771 if (gameMode == EditPosition) {
13772 DrawPosition(FALSE, boards[0]);
13777 SetWhiteToPlayEvent();
13781 SetBlackToPlayEvent();
13785 if (gameMode == IcsExamining) {
13786 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13787 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13790 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13791 if(x == BOARD_LEFT-2) {
13792 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13793 boards[0][y][1] = 0;
13795 if(x == BOARD_RGHT+1) {
13796 if(y >= gameInfo.holdingsSize) break;
13797 boards[0][y][BOARD_WIDTH-2] = 0;
13800 boards[0][y][x] = EmptySquare;
13801 DrawPosition(FALSE, boards[0]);
13806 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13807 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13808 selection = (ChessSquare) (PROMOTED piece);
13809 } else if(piece == EmptySquare) selection = WhiteSilver;
13810 else selection = (ChessSquare)((int)piece - 1);
13814 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13815 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13816 selection = (ChessSquare) (DEMOTED piece);
13817 } else if(piece == EmptySquare) selection = BlackSilver;
13818 else selection = (ChessSquare)((int)piece + 1);
13823 if(gameInfo.variant == VariantShatranj ||
13824 gameInfo.variant == VariantXiangqi ||
13825 gameInfo.variant == VariantCourier ||
13826 gameInfo.variant == VariantMakruk )
13827 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13832 if(gameInfo.variant == VariantXiangqi)
13833 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13834 if(gameInfo.variant == VariantKnightmate)
13835 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13838 if (gameMode == IcsExamining) {
13839 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13840 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13841 PieceToChar(selection), AAA + x, ONE + y);
13844 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13846 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13847 n = PieceToNumber(selection - BlackPawn);
13848 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13849 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13850 boards[0][BOARD_HEIGHT-1-n][1]++;
13852 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13853 n = PieceToNumber(selection);
13854 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13855 boards[0][n][BOARD_WIDTH-1] = selection;
13856 boards[0][n][BOARD_WIDTH-2]++;
13859 boards[0][y][x] = selection;
13860 DrawPosition(TRUE, boards[0]);
13868 DropMenuEvent (ChessSquare selection, int x, int y)
13870 ChessMove moveType;
13872 switch (gameMode) {
13873 case IcsPlayingWhite:
13874 case MachinePlaysBlack:
13875 if (!WhiteOnMove(currentMove)) {
13876 DisplayMoveError(_("It is Black's turn"));
13879 moveType = WhiteDrop;
13881 case IcsPlayingBlack:
13882 case MachinePlaysWhite:
13883 if (WhiteOnMove(currentMove)) {
13884 DisplayMoveError(_("It is White's turn"));
13887 moveType = BlackDrop;
13890 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13896 if (moveType == BlackDrop && selection < BlackPawn) {
13897 selection = (ChessSquare) ((int) selection
13898 + (int) BlackPawn - (int) WhitePawn);
13900 if (boards[currentMove][y][x] != EmptySquare) {
13901 DisplayMoveError(_("That square is occupied"));
13905 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13911 /* Accept a pending offer of any kind from opponent */
13913 if (appData.icsActive) {
13914 SendToICS(ics_prefix);
13915 SendToICS("accept\n");
13916 } else if (cmailMsgLoaded) {
13917 if (currentMove == cmailOldMove &&
13918 commentList[cmailOldMove] != NULL &&
13919 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13920 "Black offers a draw" : "White offers a draw")) {
13922 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13923 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13925 DisplayError(_("There is no pending offer on this move"), 0);
13926 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13929 /* Not used for offers from chess program */
13936 /* Decline a pending offer of any kind from opponent */
13938 if (appData.icsActive) {
13939 SendToICS(ics_prefix);
13940 SendToICS("decline\n");
13941 } else if (cmailMsgLoaded) {
13942 if (currentMove == cmailOldMove &&
13943 commentList[cmailOldMove] != NULL &&
13944 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13945 "Black offers a draw" : "White offers a draw")) {
13947 AppendComment(cmailOldMove, "Draw declined", TRUE);
13948 DisplayComment(cmailOldMove - 1, "Draw declined");
13951 DisplayError(_("There is no pending offer on this move"), 0);
13954 /* Not used for offers from chess program */
13961 /* Issue ICS rematch command */
13962 if (appData.icsActive) {
13963 SendToICS(ics_prefix);
13964 SendToICS("rematch\n");
13971 /* Call your opponent's flag (claim a win on time) */
13972 if (appData.icsActive) {
13973 SendToICS(ics_prefix);
13974 SendToICS("flag\n");
13976 switch (gameMode) {
13979 case MachinePlaysWhite:
13982 GameEnds(GameIsDrawn, "Both players ran out of time",
13985 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13987 DisplayError(_("Your opponent is not out of time"), 0);
13990 case MachinePlaysBlack:
13993 GameEnds(GameIsDrawn, "Both players ran out of time",
13996 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13998 DisplayError(_("Your opponent is not out of time"), 0);
14006 ClockClick (int which)
14007 { // [HGM] code moved to back-end from winboard.c
14008 if(which) { // black clock
14009 if (gameMode == EditPosition || gameMode == IcsExamining) {
14010 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14011 SetBlackToPlayEvent();
14012 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14013 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14014 } else if (shiftKey) {
14015 AdjustClock(which, -1);
14016 } else if (gameMode == IcsPlayingWhite ||
14017 gameMode == MachinePlaysBlack) {
14020 } else { // white clock
14021 if (gameMode == EditPosition || gameMode == IcsExamining) {
14022 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14023 SetWhiteToPlayEvent();
14024 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14025 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14026 } else if (shiftKey) {
14027 AdjustClock(which, -1);
14028 } else if (gameMode == IcsPlayingBlack ||
14029 gameMode == MachinePlaysWhite) {
14038 /* Offer draw or accept pending draw offer from opponent */
14040 if (appData.icsActive) {
14041 /* Note: tournament rules require draw offers to be
14042 made after you make your move but before you punch
14043 your clock. Currently ICS doesn't let you do that;
14044 instead, you immediately punch your clock after making
14045 a move, but you can offer a draw at any time. */
14047 SendToICS(ics_prefix);
14048 SendToICS("draw\n");
14049 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14050 } else if (cmailMsgLoaded) {
14051 if (currentMove == cmailOldMove &&
14052 commentList[cmailOldMove] != NULL &&
14053 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14054 "Black offers a draw" : "White offers a draw")) {
14055 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14056 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14057 } else if (currentMove == cmailOldMove + 1) {
14058 char *offer = WhiteOnMove(cmailOldMove) ?
14059 "White offers a draw" : "Black offers a draw";
14060 AppendComment(currentMove, offer, TRUE);
14061 DisplayComment(currentMove - 1, offer);
14062 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14064 DisplayError(_("You must make your move before offering a draw"), 0);
14065 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14067 } else if (first.offeredDraw) {
14068 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14070 if (first.sendDrawOffers) {
14071 SendToProgram("draw\n", &first);
14072 userOfferedDraw = TRUE;
14080 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14082 if (appData.icsActive) {
14083 SendToICS(ics_prefix);
14084 SendToICS("adjourn\n");
14086 /* Currently GNU Chess doesn't offer or accept Adjourns */
14094 /* Offer Abort or accept pending Abort offer from opponent */
14096 if (appData.icsActive) {
14097 SendToICS(ics_prefix);
14098 SendToICS("abort\n");
14100 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14107 /* Resign. You can do this even if it's not your turn. */
14109 if (appData.icsActive) {
14110 SendToICS(ics_prefix);
14111 SendToICS("resign\n");
14113 switch (gameMode) {
14114 case MachinePlaysWhite:
14115 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14117 case MachinePlaysBlack:
14118 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14121 if (cmailMsgLoaded) {
14123 if (WhiteOnMove(cmailOldMove)) {
14124 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14126 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14128 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14139 StopObservingEvent ()
14141 /* Stop observing current games */
14142 SendToICS(ics_prefix);
14143 SendToICS("unobserve\n");
14147 StopExaminingEvent ()
14149 /* Stop observing current game */
14150 SendToICS(ics_prefix);
14151 SendToICS("unexamine\n");
14155 ForwardInner (int target)
14159 if (appData.debugMode)
14160 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14161 target, currentMove, forwardMostMove);
14163 if (gameMode == EditPosition)
14166 seekGraphUp = FALSE;
14167 MarkTargetSquares(1);
14169 if (gameMode == PlayFromGameFile && !pausing)
14172 if (gameMode == IcsExamining && pausing)
14173 limit = pauseExamForwardMostMove;
14175 limit = forwardMostMove;
14177 if (target > limit) target = limit;
14179 if (target > 0 && moveList[target - 1][0]) {
14180 int fromX, fromY, toX, toY;
14181 toX = moveList[target - 1][2] - AAA;
14182 toY = moveList[target - 1][3] - ONE;
14183 if (moveList[target - 1][1] == '@') {
14184 if (appData.highlightLastMove) {
14185 SetHighlights(-1, -1, toX, toY);
14188 fromX = moveList[target - 1][0] - AAA;
14189 fromY = moveList[target - 1][1] - ONE;
14190 if (target == currentMove + 1) {
14191 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14193 if (appData.highlightLastMove) {
14194 SetHighlights(fromX, fromY, toX, toY);
14198 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14199 gameMode == Training || gameMode == PlayFromGameFile ||
14200 gameMode == AnalyzeFile) {
14201 while (currentMove < target) {
14202 SendMoveToProgram(currentMove++, &first);
14205 currentMove = target;
14208 if (gameMode == EditGame || gameMode == EndOfGame) {
14209 whiteTimeRemaining = timeRemaining[0][currentMove];
14210 blackTimeRemaining = timeRemaining[1][currentMove];
14212 DisplayBothClocks();
14213 DisplayMove(currentMove - 1);
14214 DrawPosition(FALSE, boards[currentMove]);
14215 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14216 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14217 DisplayComment(currentMove - 1, commentList[currentMove]);
14225 if (gameMode == IcsExamining && !pausing) {
14226 SendToICS(ics_prefix);
14227 SendToICS("forward\n");
14229 ForwardInner(currentMove + 1);
14236 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14237 /* to optimze, we temporarily turn off analysis mode while we feed
14238 * the remaining moves to the engine. Otherwise we get analysis output
14241 if (first.analysisSupport) {
14242 SendToProgram("exit\nforce\n", &first);
14243 first.analyzing = FALSE;
14247 if (gameMode == IcsExamining && !pausing) {
14248 SendToICS(ics_prefix);
14249 SendToICS("forward 999999\n");
14251 ForwardInner(forwardMostMove);
14254 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14255 /* we have fed all the moves, so reactivate analysis mode */
14256 SendToProgram("analyze\n", &first);
14257 first.analyzing = TRUE;
14258 /*first.maybeThinking = TRUE;*/
14259 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14264 BackwardInner (int target)
14266 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14268 if (appData.debugMode)
14269 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14270 target, currentMove, forwardMostMove);
14272 if (gameMode == EditPosition) return;
14273 seekGraphUp = FALSE;
14274 MarkTargetSquares(1);
14275 if (currentMove <= backwardMostMove) {
14277 DrawPosition(full_redraw, boards[currentMove]);
14280 if (gameMode == PlayFromGameFile && !pausing)
14283 if (moveList[target][0]) {
14284 int fromX, fromY, toX, toY;
14285 toX = moveList[target][2] - AAA;
14286 toY = moveList[target][3] - ONE;
14287 if (moveList[target][1] == '@') {
14288 if (appData.highlightLastMove) {
14289 SetHighlights(-1, -1, toX, toY);
14292 fromX = moveList[target][0] - AAA;
14293 fromY = moveList[target][1] - ONE;
14294 if (target == currentMove - 1) {
14295 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14297 if (appData.highlightLastMove) {
14298 SetHighlights(fromX, fromY, toX, toY);
14302 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14303 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14304 while (currentMove > target) {
14305 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14306 // null move cannot be undone. Reload program with move history before it.
14308 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14309 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14311 SendBoard(&first, i);
14312 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14315 SendToProgram("undo\n", &first);
14319 currentMove = target;
14322 if (gameMode == EditGame || gameMode == EndOfGame) {
14323 whiteTimeRemaining = timeRemaining[0][currentMove];
14324 blackTimeRemaining = timeRemaining[1][currentMove];
14326 DisplayBothClocks();
14327 DisplayMove(currentMove - 1);
14328 DrawPosition(full_redraw, boards[currentMove]);
14329 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14330 // [HGM] PV info: routine tests if comment empty
14331 DisplayComment(currentMove - 1, commentList[currentMove]);
14337 if (gameMode == IcsExamining && !pausing) {
14338 SendToICS(ics_prefix);
14339 SendToICS("backward\n");
14341 BackwardInner(currentMove - 1);
14348 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14349 /* to optimize, we temporarily turn off analysis mode while we undo
14350 * all the moves. Otherwise we get analysis output after each undo.
14352 if (first.analysisSupport) {
14353 SendToProgram("exit\nforce\n", &first);
14354 first.analyzing = FALSE;
14358 if (gameMode == IcsExamining && !pausing) {
14359 SendToICS(ics_prefix);
14360 SendToICS("backward 999999\n");
14362 BackwardInner(backwardMostMove);
14365 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14366 /* we have fed all the moves, so reactivate analysis mode */
14367 SendToProgram("analyze\n", &first);
14368 first.analyzing = TRUE;
14369 /*first.maybeThinking = TRUE;*/
14370 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14377 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14378 if (to >= forwardMostMove) to = forwardMostMove;
14379 if (to <= backwardMostMove) to = backwardMostMove;
14380 if (to < currentMove) {
14388 RevertEvent (Boolean annotate)
14390 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14393 if (gameMode != IcsExamining) {
14394 DisplayError(_("You are not examining a game"), 0);
14398 DisplayError(_("You can't revert while pausing"), 0);
14401 SendToICS(ics_prefix);
14402 SendToICS("revert\n");
14406 RetractMoveEvent ()
14408 switch (gameMode) {
14409 case MachinePlaysWhite:
14410 case MachinePlaysBlack:
14411 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14412 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14415 if (forwardMostMove < 2) return;
14416 currentMove = forwardMostMove = forwardMostMove - 2;
14417 whiteTimeRemaining = timeRemaining[0][currentMove];
14418 blackTimeRemaining = timeRemaining[1][currentMove];
14419 DisplayBothClocks();
14420 DisplayMove(currentMove - 1);
14421 ClearHighlights();/*!! could figure this out*/
14422 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14423 SendToProgram("remove\n", &first);
14424 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14427 case BeginningOfGame:
14431 case IcsPlayingWhite:
14432 case IcsPlayingBlack:
14433 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14434 SendToICS(ics_prefix);
14435 SendToICS("takeback 2\n");
14437 SendToICS(ics_prefix);
14438 SendToICS("takeback 1\n");
14447 ChessProgramState *cps;
14449 switch (gameMode) {
14450 case MachinePlaysWhite:
14451 if (!WhiteOnMove(forwardMostMove)) {
14452 DisplayError(_("It is your turn"), 0);
14457 case MachinePlaysBlack:
14458 if (WhiteOnMove(forwardMostMove)) {
14459 DisplayError(_("It is your turn"), 0);
14464 case TwoMachinesPlay:
14465 if (WhiteOnMove(forwardMostMove) ==
14466 (first.twoMachinesColor[0] == 'w')) {
14472 case BeginningOfGame:
14476 SendToProgram("?\n", cps);
14480 TruncateGameEvent ()
14483 if (gameMode != EditGame) return;
14490 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14491 if (forwardMostMove > currentMove) {
14492 if (gameInfo.resultDetails != NULL) {
14493 free(gameInfo.resultDetails);
14494 gameInfo.resultDetails = NULL;
14495 gameInfo.result = GameUnfinished;
14497 forwardMostMove = currentMove;
14498 HistorySet(parseList, backwardMostMove, forwardMostMove,
14506 if (appData.noChessProgram) return;
14507 switch (gameMode) {
14508 case MachinePlaysWhite:
14509 if (WhiteOnMove(forwardMostMove)) {
14510 DisplayError(_("Wait until your turn"), 0);
14514 case BeginningOfGame:
14515 case MachinePlaysBlack:
14516 if (!WhiteOnMove(forwardMostMove)) {
14517 DisplayError(_("Wait until your turn"), 0);
14522 DisplayError(_("No hint available"), 0);
14525 SendToProgram("hint\n", &first);
14526 hintRequested = TRUE;
14532 if (appData.noChessProgram) return;
14533 switch (gameMode) {
14534 case MachinePlaysWhite:
14535 if (WhiteOnMove(forwardMostMove)) {
14536 DisplayError(_("Wait until your turn"), 0);
14540 case BeginningOfGame:
14541 case MachinePlaysBlack:
14542 if (!WhiteOnMove(forwardMostMove)) {
14543 DisplayError(_("Wait until your turn"), 0);
14548 EditPositionDone(TRUE);
14550 case TwoMachinesPlay:
14555 SendToProgram("bk\n", &first);
14556 bookOutput[0] = NULLCHAR;
14557 bookRequested = TRUE;
14563 char *tags = PGNTags(&gameInfo);
14564 TagsPopUp(tags, CmailMsg());
14568 /* end button procedures */
14571 PrintPosition (FILE *fp, int move)
14575 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14576 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14577 char c = PieceToChar(boards[move][i][j]);
14578 fputc(c == 'x' ? '.' : c, fp);
14579 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14582 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14583 fprintf(fp, "white to play\n");
14585 fprintf(fp, "black to play\n");
14589 PrintOpponents (FILE *fp)
14591 if (gameInfo.white != NULL) {
14592 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14598 /* Find last component of program's own name, using some heuristics */
14600 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14603 int local = (strcmp(host, "localhost") == 0);
14604 while (!local && (p = strchr(prog, ';')) != NULL) {
14606 while (*p == ' ') p++;
14609 if (*prog == '"' || *prog == '\'') {
14610 q = strchr(prog + 1, *prog);
14612 q = strchr(prog, ' ');
14614 if (q == NULL) q = prog + strlen(prog);
14616 while (p >= prog && *p != '/' && *p != '\\') p--;
14618 if(p == prog && *p == '"') p++;
14619 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14620 memcpy(buf, p, q - p);
14621 buf[q - p] = NULLCHAR;
14629 TimeControlTagValue ()
14632 if (!appData.clockMode) {
14633 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14634 } else if (movesPerSession > 0) {
14635 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14636 } else if (timeIncrement == 0) {
14637 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14639 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14641 return StrSave(buf);
14647 /* This routine is used only for certain modes */
14648 VariantClass v = gameInfo.variant;
14649 ChessMove r = GameUnfinished;
14652 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14653 r = gameInfo.result;
14654 p = gameInfo.resultDetails;
14655 gameInfo.resultDetails = NULL;
14657 ClearGameInfo(&gameInfo);
14658 gameInfo.variant = v;
14660 switch (gameMode) {
14661 case MachinePlaysWhite:
14662 gameInfo.event = StrSave( appData.pgnEventHeader );
14663 gameInfo.site = StrSave(HostName());
14664 gameInfo.date = PGNDate();
14665 gameInfo.round = StrSave("-");
14666 gameInfo.white = StrSave(first.tidy);
14667 gameInfo.black = StrSave(UserName());
14668 gameInfo.timeControl = TimeControlTagValue();
14671 case MachinePlaysBlack:
14672 gameInfo.event = StrSave( appData.pgnEventHeader );
14673 gameInfo.site = StrSave(HostName());
14674 gameInfo.date = PGNDate();
14675 gameInfo.round = StrSave("-");
14676 gameInfo.white = StrSave(UserName());
14677 gameInfo.black = StrSave(first.tidy);
14678 gameInfo.timeControl = TimeControlTagValue();
14681 case TwoMachinesPlay:
14682 gameInfo.event = StrSave( appData.pgnEventHeader );
14683 gameInfo.site = StrSave(HostName());
14684 gameInfo.date = PGNDate();
14687 snprintf(buf, MSG_SIZ, "%d", roundNr);
14688 gameInfo.round = StrSave(buf);
14690 gameInfo.round = StrSave("-");
14692 if (first.twoMachinesColor[0] == 'w') {
14693 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14694 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14696 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14697 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14699 gameInfo.timeControl = TimeControlTagValue();
14703 gameInfo.event = StrSave("Edited game");
14704 gameInfo.site = StrSave(HostName());
14705 gameInfo.date = PGNDate();
14706 gameInfo.round = StrSave("-");
14707 gameInfo.white = StrSave("-");
14708 gameInfo.black = StrSave("-");
14709 gameInfo.result = r;
14710 gameInfo.resultDetails = p;
14714 gameInfo.event = StrSave("Edited position");
14715 gameInfo.site = StrSave(HostName());
14716 gameInfo.date = PGNDate();
14717 gameInfo.round = StrSave("-");
14718 gameInfo.white = StrSave("-");
14719 gameInfo.black = StrSave("-");
14722 case IcsPlayingWhite:
14723 case IcsPlayingBlack:
14728 case PlayFromGameFile:
14729 gameInfo.event = StrSave("Game from non-PGN file");
14730 gameInfo.site = StrSave(HostName());
14731 gameInfo.date = PGNDate();
14732 gameInfo.round = StrSave("-");
14733 gameInfo.white = StrSave("?");
14734 gameInfo.black = StrSave("?");
14743 ReplaceComment (int index, char *text)
14749 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14750 pvInfoList[index-1].depth == len &&
14751 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14752 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14753 while (*text == '\n') text++;
14754 len = strlen(text);
14755 while (len > 0 && text[len - 1] == '\n') len--;
14757 if (commentList[index] != NULL)
14758 free(commentList[index]);
14761 commentList[index] = NULL;
14764 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14765 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14766 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14767 commentList[index] = (char *) malloc(len + 2);
14768 strncpy(commentList[index], text, len);
14769 commentList[index][len] = '\n';
14770 commentList[index][len + 1] = NULLCHAR;
14772 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14774 commentList[index] = (char *) malloc(len + 7);
14775 safeStrCpy(commentList[index], "{\n", 3);
14776 safeStrCpy(commentList[index]+2, text, len+1);
14777 commentList[index][len+2] = NULLCHAR;
14778 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14779 strcat(commentList[index], "\n}\n");
14784 CrushCRs (char *text)
14792 if (ch == '\r') continue;
14794 } while (ch != '\0');
14798 AppendComment (int index, char *text, Boolean addBraces)
14799 /* addBraces tells if we should add {} */
14804 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14805 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14808 while (*text == '\n') text++;
14809 len = strlen(text);
14810 while (len > 0 && text[len - 1] == '\n') len--;
14811 text[len] = NULLCHAR;
14813 if (len == 0) return;
14815 if (commentList[index] != NULL) {
14816 Boolean addClosingBrace = addBraces;
14817 old = commentList[index];
14818 oldlen = strlen(old);
14819 while(commentList[index][oldlen-1] == '\n')
14820 commentList[index][--oldlen] = NULLCHAR;
14821 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14822 safeStrCpy(commentList[index], old, oldlen + len + 6);
14824 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14825 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14826 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14827 while (*text == '\n') { text++; len--; }
14828 commentList[index][--oldlen] = NULLCHAR;
14830 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14831 else strcat(commentList[index], "\n");
14832 strcat(commentList[index], text);
14833 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14834 else strcat(commentList[index], "\n");
14836 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14838 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14839 else commentList[index][0] = NULLCHAR;
14840 strcat(commentList[index], text);
14841 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14842 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14847 FindStr (char * text, char * sub_text)
14849 char * result = strstr( text, sub_text );
14851 if( result != NULL ) {
14852 result += strlen( sub_text );
14858 /* [AS] Try to extract PV info from PGN comment */
14859 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14861 GetInfoFromComment (int index, char * text)
14863 char * sep = text, *p;
14865 if( text != NULL && index > 0 ) {
14868 int time = -1, sec = 0, deci;
14869 char * s_eval = FindStr( text, "[%eval " );
14870 char * s_emt = FindStr( text, "[%emt " );
14872 if( s_eval != NULL || s_emt != NULL ) {
14876 if( s_eval != NULL ) {
14877 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14881 if( delim != ']' ) {
14886 if( s_emt != NULL ) {
14891 /* We expect something like: [+|-]nnn.nn/dd */
14894 if(*text != '{') return text; // [HGM] braces: must be normal comment
14896 sep = strchr( text, '/' );
14897 if( sep == NULL || sep < (text+4) ) {
14902 if(p[1] == '(') { // comment starts with PV
14903 p = strchr(p, ')'); // locate end of PV
14904 if(p == NULL || sep < p+5) return text;
14905 // at this point we have something like "{(.*) +0.23/6 ..."
14906 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14907 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14908 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14910 time = -1; sec = -1; deci = -1;
14911 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14912 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14913 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14914 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14918 if( score_lo < 0 || score_lo >= 100 ) {
14922 if(sec >= 0) time = 600*time + 10*sec; else
14923 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14925 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14927 /* [HGM] PV time: now locate end of PV info */
14928 while( *++sep >= '0' && *sep <= '9'); // strip depth
14930 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14932 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14934 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14935 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14946 pvInfoList[index-1].depth = depth;
14947 pvInfoList[index-1].score = score;
14948 pvInfoList[index-1].time = 10*time; // centi-sec
14949 if(*sep == '}') *sep = 0; else *--sep = '{';
14950 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14956 SendToProgram (char *message, ChessProgramState *cps)
14958 int count, outCount, error;
14961 if (cps->pr == NoProc) return;
14964 if (appData.debugMode) {
14967 fprintf(debugFP, "%ld >%-6s: %s",
14968 SubtractTimeMarks(&now, &programStartTime),
14969 cps->which, message);
14972 count = strlen(message);
14973 outCount = OutputToProcess(cps->pr, message, count, &error);
14974 if (outCount < count && !exiting
14975 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14976 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14977 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14978 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14979 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14980 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14981 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14982 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14984 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14985 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14986 gameInfo.result = res;
14988 gameInfo.resultDetails = StrSave(buf);
14990 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14991 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14996 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15000 ChessProgramState *cps = (ChessProgramState *)closure;
15002 if (isr != cps->isr) return; /* Killed intentionally */
15005 RemoveInputSource(cps->isr);
15006 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15007 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15008 _(cps->which), cps->program);
15009 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15010 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15011 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15012 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15013 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15015 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15016 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15017 gameInfo.result = res;
15019 gameInfo.resultDetails = StrSave(buf);
15021 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15022 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15024 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15025 _(cps->which), cps->program);
15026 RemoveInputSource(cps->isr);
15028 /* [AS] Program is misbehaving badly... kill it */
15029 if( count == -2 ) {
15030 DestroyChildProcess( cps->pr, 9 );
15034 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15039 if ((end_str = strchr(message, '\r')) != NULL)
15040 *end_str = NULLCHAR;
15041 if ((end_str = strchr(message, '\n')) != NULL)
15042 *end_str = NULLCHAR;
15044 if (appData.debugMode) {
15045 TimeMark now; int print = 1;
15046 char *quote = ""; char c; int i;
15048 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15049 char start = message[0];
15050 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15051 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15052 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15053 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15054 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15055 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15056 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15057 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15058 sscanf(message, "hint: %c", &c)!=1 &&
15059 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15060 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15061 print = (appData.engineComments >= 2);
15063 message[0] = start; // restore original message
15067 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15068 SubtractTimeMarks(&now, &programStartTime), cps->which,
15074 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15075 if (appData.icsEngineAnalyze) {
15076 if (strstr(message, "whisper") != NULL ||
15077 strstr(message, "kibitz") != NULL ||
15078 strstr(message, "tellics") != NULL) return;
15081 HandleMachineMove(message, cps);
15086 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15091 if( timeControl_2 > 0 ) {
15092 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15093 tc = timeControl_2;
15096 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15097 inc /= cps->timeOdds;
15098 st /= cps->timeOdds;
15100 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15103 /* Set exact time per move, normally using st command */
15104 if (cps->stKludge) {
15105 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15107 if (seconds == 0) {
15108 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15110 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15113 snprintf(buf, MSG_SIZ, "st %d\n", st);
15116 /* Set conventional or incremental time control, using level command */
15117 if (seconds == 0) {
15118 /* Note old gnuchess bug -- minutes:seconds used to not work.
15119 Fixed in later versions, but still avoid :seconds
15120 when seconds is 0. */
15121 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15123 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15124 seconds, inc/1000.);
15127 SendToProgram(buf, cps);
15129 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15130 /* Orthogonally, limit search to given depth */
15132 if (cps->sdKludge) {
15133 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15135 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15137 SendToProgram(buf, cps);
15140 if(cps->nps >= 0) { /* [HGM] nps */
15141 if(cps->supportsNPS == FALSE)
15142 cps->nps = -1; // don't use if engine explicitly says not supported!
15144 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15145 SendToProgram(buf, cps);
15150 ChessProgramState *
15152 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15154 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15155 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15161 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15163 char message[MSG_SIZ];
15166 /* Note: this routine must be called when the clocks are stopped
15167 or when they have *just* been set or switched; otherwise
15168 it will be off by the time since the current tick started.
15170 if (machineWhite) {
15171 time = whiteTimeRemaining / 10;
15172 otime = blackTimeRemaining / 10;
15174 time = blackTimeRemaining / 10;
15175 otime = whiteTimeRemaining / 10;
15177 /* [HGM] translate opponent's time by time-odds factor */
15178 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15179 if (appData.debugMode) {
15180 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15183 if (time <= 0) time = 1;
15184 if (otime <= 0) otime = 1;
15186 snprintf(message, MSG_SIZ, "time %ld\n", time);
15187 SendToProgram(message, cps);
15189 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15190 SendToProgram(message, cps);
15194 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15197 int len = strlen(name);
15200 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15202 sscanf(*p, "%d", &val);
15204 while (**p && **p != ' ')
15206 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15207 SendToProgram(buf, cps);
15214 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15217 int len = strlen(name);
15218 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15220 sscanf(*p, "%d", loc);
15221 while (**p && **p != ' ') (*p)++;
15222 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15223 SendToProgram(buf, cps);
15230 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15233 int len = strlen(name);
15234 if (strncmp((*p), name, len) == 0
15235 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15237 sscanf(*p, "%[^\"]", loc);
15238 while (**p && **p != '\"') (*p)++;
15239 if (**p == '\"') (*p)++;
15240 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15241 SendToProgram(buf, cps);
15248 ParseOption (Option *opt, ChessProgramState *cps)
15249 // [HGM] options: process the string that defines an engine option, and determine
15250 // name, type, default value, and allowed value range
15252 char *p, *q, buf[MSG_SIZ];
15253 int n, min = (-1)<<31, max = 1<<31, def;
15255 if(p = strstr(opt->name, " -spin ")) {
15256 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15257 if(max < min) max = min; // enforce consistency
15258 if(def < min) def = min;
15259 if(def > max) def = max;
15264 } else if((p = strstr(opt->name, " -slider "))) {
15265 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15266 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15267 if(max < min) max = min; // enforce consistency
15268 if(def < min) def = min;
15269 if(def > max) def = max;
15273 opt->type = Spin; // Slider;
15274 } else if((p = strstr(opt->name, " -string "))) {
15275 opt->textValue = p+9;
15276 opt->type = TextBox;
15277 } else if((p = strstr(opt->name, " -file "))) {
15278 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15279 opt->textValue = p+7;
15280 opt->type = FileName; // FileName;
15281 } else if((p = strstr(opt->name, " -path "))) {
15282 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15283 opt->textValue = p+7;
15284 opt->type = PathName; // PathName;
15285 } else if(p = strstr(opt->name, " -check ")) {
15286 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15287 opt->value = (def != 0);
15288 opt->type = CheckBox;
15289 } else if(p = strstr(opt->name, " -combo ")) {
15290 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15291 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15292 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15293 opt->value = n = 0;
15294 while(q = StrStr(q, " /// ")) {
15295 n++; *q = 0; // count choices, and null-terminate each of them
15297 if(*q == '*') { // remember default, which is marked with * prefix
15301 cps->comboList[cps->comboCnt++] = q;
15303 cps->comboList[cps->comboCnt++] = NULL;
15305 opt->type = ComboBox;
15306 } else if(p = strstr(opt->name, " -button")) {
15307 opt->type = Button;
15308 } else if(p = strstr(opt->name, " -save")) {
15309 opt->type = SaveButton;
15310 } else return FALSE;
15311 *p = 0; // terminate option name
15312 // now look if the command-line options define a setting for this engine option.
15313 if(cps->optionSettings && cps->optionSettings[0])
15314 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15315 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15316 snprintf(buf, MSG_SIZ, "option %s", p);
15317 if(p = strstr(buf, ",")) *p = 0;
15318 if(q = strchr(buf, '=')) switch(opt->type) {
15320 for(n=0; n<opt->max; n++)
15321 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15324 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15328 opt->value = atoi(q+1);
15333 SendToProgram(buf, cps);
15339 FeatureDone (ChessProgramState *cps, int val)
15341 DelayedEventCallback cb = GetDelayedEvent();
15342 if ((cb == InitBackEnd3 && cps == &first) ||
15343 (cb == SettingsMenuIfReady && cps == &second) ||
15344 (cb == LoadEngine) ||
15345 (cb == TwoMachinesEventIfReady)) {
15346 CancelDelayedEvent();
15347 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15349 cps->initDone = val;
15352 /* Parse feature command from engine */
15354 ParseFeatures (char *args, ChessProgramState *cps)
15362 while (*p == ' ') p++;
15363 if (*p == NULLCHAR) return;
15365 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15366 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15367 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15368 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15369 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15370 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15371 if (BoolFeature(&p, "reuse", &val, cps)) {
15372 /* Engine can disable reuse, but can't enable it if user said no */
15373 if (!val) cps->reuse = FALSE;
15376 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15377 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15378 if (gameMode == TwoMachinesPlay) {
15379 DisplayTwoMachinesTitle();
15385 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15386 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15387 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15388 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15389 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15390 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15391 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15392 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15393 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15394 if (IntFeature(&p, "done", &val, cps)) {
15395 FeatureDone(cps, val);
15398 /* Added by Tord: */
15399 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15400 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15401 /* End of additions by Tord */
15403 /* [HGM] added features: */
15404 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15405 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15406 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15407 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15408 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15409 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15410 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15411 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15412 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15413 SendToProgram(buf, cps);
15416 if(cps->nrOptions >= MAX_OPTIONS) {
15418 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15419 DisplayError(buf, 0);
15423 /* End of additions by HGM */
15425 /* unknown feature: complain and skip */
15427 while (*q && *q != '=') q++;
15428 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15429 SendToProgram(buf, cps);
15435 while (*p && *p != '\"') p++;
15436 if (*p == '\"') p++;
15438 while (*p && *p != ' ') p++;
15446 PeriodicUpdatesEvent (int newState)
15448 if (newState == appData.periodicUpdates)
15451 appData.periodicUpdates=newState;
15453 /* Display type changes, so update it now */
15454 // DisplayAnalysis();
15456 /* Get the ball rolling again... */
15458 AnalysisPeriodicEvent(1);
15459 StartAnalysisClock();
15464 PonderNextMoveEvent (int newState)
15466 if (newState == appData.ponderNextMove) return;
15467 if (gameMode == EditPosition) EditPositionDone(TRUE);
15469 SendToProgram("hard\n", &first);
15470 if (gameMode == TwoMachinesPlay) {
15471 SendToProgram("hard\n", &second);
15474 SendToProgram("easy\n", &first);
15475 thinkOutput[0] = NULLCHAR;
15476 if (gameMode == TwoMachinesPlay) {
15477 SendToProgram("easy\n", &second);
15480 appData.ponderNextMove = newState;
15484 NewSettingEvent (int option, int *feature, char *command, int value)
15488 if (gameMode == EditPosition) EditPositionDone(TRUE);
15489 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15490 if(feature == NULL || *feature) SendToProgram(buf, &first);
15491 if (gameMode == TwoMachinesPlay) {
15492 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15497 ShowThinkingEvent ()
15498 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15500 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15501 int newState = appData.showThinking
15502 // [HGM] thinking: other features now need thinking output as well
15503 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15505 if (oldState == newState) return;
15506 oldState = newState;
15507 if (gameMode == EditPosition) EditPositionDone(TRUE);
15509 SendToProgram("post\n", &first);
15510 if (gameMode == TwoMachinesPlay) {
15511 SendToProgram("post\n", &second);
15514 SendToProgram("nopost\n", &first);
15515 thinkOutput[0] = NULLCHAR;
15516 if (gameMode == TwoMachinesPlay) {
15517 SendToProgram("nopost\n", &second);
15520 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15524 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15526 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15527 if (pr == NoProc) return;
15528 AskQuestion(title, question, replyPrefix, pr);
15532 TypeInEvent (char firstChar)
15534 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15535 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15536 gameMode == AnalyzeMode || gameMode == EditGame ||
15537 gameMode == EditPosition || gameMode == IcsExamining ||
15538 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15539 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15540 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15541 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15542 gameMode == Training) PopUpMoveDialog(firstChar);
15546 TypeInDoneEvent (char *move)
15549 int n, fromX, fromY, toX, toY;
15551 ChessMove moveType;
15554 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15555 EditPositionPasteFEN(move);
15558 // [HGM] movenum: allow move number to be typed in any mode
15559 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15564 if (gameMode != EditGame && currentMove != forwardMostMove &&
15565 gameMode != Training) {
15566 DisplayMoveError(_("Displayed move is not current"));
15568 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15569 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15570 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15571 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15572 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15573 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15575 DisplayMoveError(_("Could not parse move"));
15581 DisplayMove (int moveNumber)
15583 char message[MSG_SIZ];
15585 char cpThinkOutput[MSG_SIZ];
15587 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15589 if (moveNumber == forwardMostMove - 1 ||
15590 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15592 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15594 if (strchr(cpThinkOutput, '\n')) {
15595 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15598 *cpThinkOutput = NULLCHAR;
15601 /* [AS] Hide thinking from human user */
15602 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15603 *cpThinkOutput = NULLCHAR;
15604 if( thinkOutput[0] != NULLCHAR ) {
15607 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15608 cpThinkOutput[i] = '.';
15610 cpThinkOutput[i] = NULLCHAR;
15611 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15615 if (moveNumber == forwardMostMove - 1 &&
15616 gameInfo.resultDetails != NULL) {
15617 if (gameInfo.resultDetails[0] == NULLCHAR) {
15618 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15620 snprintf(res, MSG_SIZ, " {%s} %s",
15621 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15627 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15628 DisplayMessage(res, cpThinkOutput);
15630 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15631 WhiteOnMove(moveNumber) ? " " : ".. ",
15632 parseList[moveNumber], res);
15633 DisplayMessage(message, cpThinkOutput);
15638 DisplayComment (int moveNumber, char *text)
15640 char title[MSG_SIZ];
15642 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15643 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15645 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15646 WhiteOnMove(moveNumber) ? " " : ".. ",
15647 parseList[moveNumber]);
15649 if (text != NULL && (appData.autoDisplayComment || commentUp))
15650 CommentPopUp(title, text);
15653 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15654 * might be busy thinking or pondering. It can be omitted if your
15655 * gnuchess is configured to stop thinking immediately on any user
15656 * input. However, that gnuchess feature depends on the FIONREAD
15657 * ioctl, which does not work properly on some flavors of Unix.
15660 Attention (ChessProgramState *cps)
15663 if (!cps->useSigint) return;
15664 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15665 switch (gameMode) {
15666 case MachinePlaysWhite:
15667 case MachinePlaysBlack:
15668 case TwoMachinesPlay:
15669 case IcsPlayingWhite:
15670 case IcsPlayingBlack:
15673 /* Skip if we know it isn't thinking */
15674 if (!cps->maybeThinking) return;
15675 if (appData.debugMode)
15676 fprintf(debugFP, "Interrupting %s\n", cps->which);
15677 InterruptChildProcess(cps->pr);
15678 cps->maybeThinking = FALSE;
15683 #endif /*ATTENTION*/
15689 if (whiteTimeRemaining <= 0) {
15692 if (appData.icsActive) {
15693 if (appData.autoCallFlag &&
15694 gameMode == IcsPlayingBlack && !blackFlag) {
15695 SendToICS(ics_prefix);
15696 SendToICS("flag\n");
15700 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15702 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15703 if (appData.autoCallFlag) {
15704 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15711 if (blackTimeRemaining <= 0) {
15714 if (appData.icsActive) {
15715 if (appData.autoCallFlag &&
15716 gameMode == IcsPlayingWhite && !whiteFlag) {
15717 SendToICS(ics_prefix);
15718 SendToICS("flag\n");
15722 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15724 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15725 if (appData.autoCallFlag) {
15726 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15737 CheckTimeControl ()
15739 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15740 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15743 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15745 if ( !WhiteOnMove(forwardMostMove) ) {
15746 /* White made time control */
15747 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15748 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15749 /* [HGM] time odds: correct new time quota for time odds! */
15750 / WhitePlayer()->timeOdds;
15751 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15753 lastBlack -= blackTimeRemaining;
15754 /* Black made time control */
15755 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15756 / WhitePlayer()->other->timeOdds;
15757 lastWhite = whiteTimeRemaining;
15762 DisplayBothClocks ()
15764 int wom = gameMode == EditPosition ?
15765 !blackPlaysFirst : WhiteOnMove(currentMove);
15766 DisplayWhiteClock(whiteTimeRemaining, wom);
15767 DisplayBlackClock(blackTimeRemaining, !wom);
15771 /* Timekeeping seems to be a portability nightmare. I think everyone
15772 has ftime(), but I'm really not sure, so I'm including some ifdefs
15773 to use other calls if you don't. Clocks will be less accurate if
15774 you have neither ftime nor gettimeofday.
15777 /* VS 2008 requires the #include outside of the function */
15778 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15779 #include <sys/timeb.h>
15782 /* Get the current time as a TimeMark */
15784 GetTimeMark (TimeMark *tm)
15786 #if HAVE_GETTIMEOFDAY
15788 struct timeval timeVal;
15789 struct timezone timeZone;
15791 gettimeofday(&timeVal, &timeZone);
15792 tm->sec = (long) timeVal.tv_sec;
15793 tm->ms = (int) (timeVal.tv_usec / 1000L);
15795 #else /*!HAVE_GETTIMEOFDAY*/
15798 // include <sys/timeb.h> / moved to just above start of function
15799 struct timeb timeB;
15802 tm->sec = (long) timeB.time;
15803 tm->ms = (int) timeB.millitm;
15805 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15806 tm->sec = (long) time(NULL);
15812 /* Return the difference in milliseconds between two
15813 time marks. We assume the difference will fit in a long!
15816 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15818 return 1000L*(tm2->sec - tm1->sec) +
15819 (long) (tm2->ms - tm1->ms);
15824 * Code to manage the game clocks.
15826 * In tournament play, black starts the clock and then white makes a move.
15827 * We give the human user a slight advantage if he is playing white---the
15828 * clocks don't run until he makes his first move, so it takes zero time.
15829 * Also, we don't account for network lag, so we could get out of sync
15830 * with GNU Chess's clock -- but then, referees are always right.
15833 static TimeMark tickStartTM;
15834 static long intendedTickLength;
15837 NextTickLength (long timeRemaining)
15839 long nominalTickLength, nextTickLength;
15841 if (timeRemaining > 0L && timeRemaining <= 10000L)
15842 nominalTickLength = 100L;
15844 nominalTickLength = 1000L;
15845 nextTickLength = timeRemaining % nominalTickLength;
15846 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15848 return nextTickLength;
15851 /* Adjust clock one minute up or down */
15853 AdjustClock (Boolean which, int dir)
15855 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15856 if(which) blackTimeRemaining += 60000*dir;
15857 else whiteTimeRemaining += 60000*dir;
15858 DisplayBothClocks();
15859 adjustedClock = TRUE;
15862 /* Stop clocks and reset to a fresh time control */
15866 (void) StopClockTimer();
15867 if (appData.icsActive) {
15868 whiteTimeRemaining = blackTimeRemaining = 0;
15869 } else if (searchTime) {
15870 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15871 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15872 } else { /* [HGM] correct new time quote for time odds */
15873 whiteTC = blackTC = fullTimeControlString;
15874 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15875 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15877 if (whiteFlag || blackFlag) {
15879 whiteFlag = blackFlag = FALSE;
15881 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15882 DisplayBothClocks();
15883 adjustedClock = FALSE;
15886 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15888 /* Decrement running clock by amount of time that has passed */
15892 long timeRemaining;
15893 long lastTickLength, fudge;
15896 if (!appData.clockMode) return;
15897 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15901 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15903 /* Fudge if we woke up a little too soon */
15904 fudge = intendedTickLength - lastTickLength;
15905 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15907 if (WhiteOnMove(forwardMostMove)) {
15908 if(whiteNPS >= 0) lastTickLength = 0;
15909 timeRemaining = whiteTimeRemaining -= lastTickLength;
15910 if(timeRemaining < 0 && !appData.icsActive) {
15911 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15912 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15913 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15914 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15917 DisplayWhiteClock(whiteTimeRemaining - fudge,
15918 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15920 if(blackNPS >= 0) lastTickLength = 0;
15921 timeRemaining = blackTimeRemaining -= lastTickLength;
15922 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15923 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15925 blackStartMove = forwardMostMove;
15926 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15929 DisplayBlackClock(blackTimeRemaining - fudge,
15930 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15932 if (CheckFlags()) return;
15935 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15936 StartClockTimer(intendedTickLength);
15938 /* if the time remaining has fallen below the alarm threshold, sound the
15939 * alarm. if the alarm has sounded and (due to a takeback or time control
15940 * with increment) the time remaining has increased to a level above the
15941 * threshold, reset the alarm so it can sound again.
15944 if (appData.icsActive && appData.icsAlarm) {
15946 /* make sure we are dealing with the user's clock */
15947 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15948 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15951 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15952 alarmSounded = FALSE;
15953 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15955 alarmSounded = TRUE;
15961 /* A player has just moved, so stop the previously running
15962 clock and (if in clock mode) start the other one.
15963 We redisplay both clocks in case we're in ICS mode, because
15964 ICS gives us an update to both clocks after every move.
15965 Note that this routine is called *after* forwardMostMove
15966 is updated, so the last fractional tick must be subtracted
15967 from the color that is *not* on move now.
15970 SwitchClocks (int newMoveNr)
15972 long lastTickLength;
15974 int flagged = FALSE;
15978 if (StopClockTimer() && appData.clockMode) {
15979 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15980 if (!WhiteOnMove(forwardMostMove)) {
15981 if(blackNPS >= 0) lastTickLength = 0;
15982 blackTimeRemaining -= lastTickLength;
15983 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15984 // if(pvInfoList[forwardMostMove].time == -1)
15985 pvInfoList[forwardMostMove].time = // use GUI time
15986 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15988 if(whiteNPS >= 0) lastTickLength = 0;
15989 whiteTimeRemaining -= lastTickLength;
15990 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15991 // if(pvInfoList[forwardMostMove].time == -1)
15992 pvInfoList[forwardMostMove].time =
15993 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15995 flagged = CheckFlags();
15997 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15998 CheckTimeControl();
16000 if (flagged || !appData.clockMode) return;
16002 switch (gameMode) {
16003 case MachinePlaysBlack:
16004 case MachinePlaysWhite:
16005 case BeginningOfGame:
16006 if (pausing) return;
16010 case PlayFromGameFile:
16018 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16019 if(WhiteOnMove(forwardMostMove))
16020 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16021 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16025 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16026 whiteTimeRemaining : blackTimeRemaining);
16027 StartClockTimer(intendedTickLength);
16031 /* Stop both clocks */
16035 long lastTickLength;
16038 if (!StopClockTimer()) return;
16039 if (!appData.clockMode) return;
16043 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16044 if (WhiteOnMove(forwardMostMove)) {
16045 if(whiteNPS >= 0) lastTickLength = 0;
16046 whiteTimeRemaining -= lastTickLength;
16047 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16049 if(blackNPS >= 0) lastTickLength = 0;
16050 blackTimeRemaining -= lastTickLength;
16051 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16056 /* Start clock of player on move. Time may have been reset, so
16057 if clock is already running, stop and restart it. */
16061 (void) StopClockTimer(); /* in case it was running already */
16062 DisplayBothClocks();
16063 if (CheckFlags()) return;
16065 if (!appData.clockMode) return;
16066 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16068 GetTimeMark(&tickStartTM);
16069 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16070 whiteTimeRemaining : blackTimeRemaining);
16072 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16073 whiteNPS = blackNPS = -1;
16074 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16075 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16076 whiteNPS = first.nps;
16077 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16078 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16079 blackNPS = first.nps;
16080 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16081 whiteNPS = second.nps;
16082 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16083 blackNPS = second.nps;
16084 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16086 StartClockTimer(intendedTickLength);
16090 TimeString (long ms)
16092 long second, minute, hour, day;
16094 static char buf[32];
16096 if (ms > 0 && ms <= 9900) {
16097 /* convert milliseconds to tenths, rounding up */
16098 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16100 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16104 /* convert milliseconds to seconds, rounding up */
16105 /* use floating point to avoid strangeness of integer division
16106 with negative dividends on many machines */
16107 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16114 day = second / (60 * 60 * 24);
16115 second = second % (60 * 60 * 24);
16116 hour = second / (60 * 60);
16117 second = second % (60 * 60);
16118 minute = second / 60;
16119 second = second % 60;
16122 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16123 sign, day, hour, minute, second);
16125 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16127 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16134 * This is necessary because some C libraries aren't ANSI C compliant yet.
16137 StrStr (char *string, char *match)
16141 length = strlen(match);
16143 for (i = strlen(string) - length; i >= 0; i--, string++)
16144 if (!strncmp(match, string, length))
16151 StrCaseStr (char *string, char *match)
16155 length = strlen(match);
16157 for (i = strlen(string) - length; i >= 0; i--, string++) {
16158 for (j = 0; j < length; j++) {
16159 if (ToLower(match[j]) != ToLower(string[j]))
16162 if (j == length) return string;
16170 StrCaseCmp (char *s1, char *s2)
16175 c1 = ToLower(*s1++);
16176 c2 = ToLower(*s2++);
16177 if (c1 > c2) return 1;
16178 if (c1 < c2) return -1;
16179 if (c1 == NULLCHAR) return 0;
16187 return isupper(c) ? tolower(c) : c;
16194 return islower(c) ? toupper(c) : c;
16196 #endif /* !_amigados */
16203 if ((ret = (char *) malloc(strlen(s) + 1)))
16205 safeStrCpy(ret, s, strlen(s)+1);
16211 StrSavePtr (char *s, char **savePtr)
16216 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16217 safeStrCpy(*savePtr, s, strlen(s)+1);
16229 clock = time((time_t *)NULL);
16230 tm = localtime(&clock);
16231 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16232 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16233 return StrSave(buf);
16238 PositionToFEN (int move, char *overrideCastling)
16240 int i, j, fromX, fromY, toX, toY;
16247 whiteToPlay = (gameMode == EditPosition) ?
16248 !blackPlaysFirst : (move % 2 == 0);
16251 /* Piece placement data */
16252 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16253 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16255 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16256 if (boards[move][i][j] == EmptySquare) {
16258 } else { ChessSquare piece = boards[move][i][j];
16259 if (emptycount > 0) {
16260 if(emptycount<10) /* [HGM] can be >= 10 */
16261 *p++ = '0' + emptycount;
16262 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16265 if(PieceToChar(piece) == '+') {
16266 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16268 piece = (ChessSquare)(DEMOTED piece);
16270 *p++ = PieceToChar(piece);
16272 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16273 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16278 if (emptycount > 0) {
16279 if(emptycount<10) /* [HGM] can be >= 10 */
16280 *p++ = '0' + emptycount;
16281 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16288 /* [HGM] print Crazyhouse or Shogi holdings */
16289 if( gameInfo.holdingsWidth ) {
16290 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16292 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16293 piece = boards[move][i][BOARD_WIDTH-1];
16294 if( piece != EmptySquare )
16295 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16296 *p++ = PieceToChar(piece);
16298 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16299 piece = boards[move][BOARD_HEIGHT-i-1][0];
16300 if( piece != EmptySquare )
16301 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16302 *p++ = PieceToChar(piece);
16305 if( q == p ) *p++ = '-';
16311 *p++ = whiteToPlay ? 'w' : 'b';
16314 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16315 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16317 if(nrCastlingRights) {
16319 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16320 /* [HGM] write directly from rights */
16321 if(boards[move][CASTLING][2] != NoRights &&
16322 boards[move][CASTLING][0] != NoRights )
16323 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16324 if(boards[move][CASTLING][2] != NoRights &&
16325 boards[move][CASTLING][1] != NoRights )
16326 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16327 if(boards[move][CASTLING][5] != NoRights &&
16328 boards[move][CASTLING][3] != NoRights )
16329 *p++ = boards[move][CASTLING][3] + AAA;
16330 if(boards[move][CASTLING][5] != NoRights &&
16331 boards[move][CASTLING][4] != NoRights )
16332 *p++ = boards[move][CASTLING][4] + AAA;
16335 /* [HGM] write true castling rights */
16336 if( nrCastlingRights == 6 ) {
16337 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16338 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16339 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16340 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16341 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16342 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16343 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16344 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16347 if (q == p) *p++ = '-'; /* No castling rights */
16351 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16352 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16353 /* En passant target square */
16354 if (move > backwardMostMove) {
16355 fromX = moveList[move - 1][0] - AAA;
16356 fromY = moveList[move - 1][1] - ONE;
16357 toX = moveList[move - 1][2] - AAA;
16358 toY = moveList[move - 1][3] - ONE;
16359 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16360 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16361 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16363 /* 2-square pawn move just happened */
16365 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16369 } else if(move == backwardMostMove) {
16370 // [HGM] perhaps we should always do it like this, and forget the above?
16371 if((signed char)boards[move][EP_STATUS] >= 0) {
16372 *p++ = boards[move][EP_STATUS] + AAA;
16373 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16384 /* [HGM] find reversible plies */
16385 { int i = 0, j=move;
16387 if (appData.debugMode) { int k;
16388 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16389 for(k=backwardMostMove; k<=forwardMostMove; k++)
16390 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16394 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16395 if( j == backwardMostMove ) i += initialRulePlies;
16396 sprintf(p, "%d ", i);
16397 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16399 /* Fullmove number */
16400 sprintf(p, "%d", (move / 2) + 1);
16402 return StrSave(buf);
16406 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16415 /* [HGM] by default clear Crazyhouse holdings, if present */
16416 if(gameInfo.holdingsWidth) {
16417 for(i=0; i<BOARD_HEIGHT; i++) {
16418 board[i][0] = EmptySquare; /* black holdings */
16419 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16420 board[i][1] = (ChessSquare) 0; /* black counts */
16421 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16425 /* Piece placement data */
16426 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16429 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16430 if (*p == '/') p++;
16431 emptycount = gameInfo.boardWidth - j;
16432 while (emptycount--)
16433 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16435 #if(BOARD_FILES >= 10)
16436 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16437 p++; emptycount=10;
16438 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16439 while (emptycount--)
16440 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16442 } else if (isdigit(*p)) {
16443 emptycount = *p++ - '0';
16444 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16445 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16446 while (emptycount--)
16447 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16448 } else if (*p == '+' || isalpha(*p)) {
16449 if (j >= gameInfo.boardWidth) return FALSE;
16451 piece = CharToPiece(*++p);
16452 if(piece == EmptySquare) return FALSE; /* unknown piece */
16453 piece = (ChessSquare) (PROMOTED piece ); p++;
16454 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16455 } else piece = CharToPiece(*p++);
16457 if(piece==EmptySquare) return FALSE; /* unknown piece */
16458 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16459 piece = (ChessSquare) (PROMOTED piece);
16460 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16463 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16469 while (*p == '/' || *p == ' ') p++;
16471 /* [HGM] look for Crazyhouse holdings here */
16472 while(*p==' ') p++;
16473 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16475 if(*p == '-' ) p++; /* empty holdings */ else {
16476 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16477 /* if we would allow FEN reading to set board size, we would */
16478 /* have to add holdings and shift the board read so far here */
16479 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16481 if((int) piece >= (int) BlackPawn ) {
16482 i = (int)piece - (int)BlackPawn;
16483 i = PieceToNumber((ChessSquare)i);
16484 if( i >= gameInfo.holdingsSize ) return FALSE;
16485 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16486 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16488 i = (int)piece - (int)WhitePawn;
16489 i = PieceToNumber((ChessSquare)i);
16490 if( i >= gameInfo.holdingsSize ) return FALSE;
16491 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16492 board[i][BOARD_WIDTH-2]++; /* black holdings */
16499 while(*p == ' ') p++;
16503 if(appData.colorNickNames) {
16504 if( c == appData.colorNickNames[0] ) c = 'w'; else
16505 if( c == appData.colorNickNames[1] ) c = 'b';
16509 *blackPlaysFirst = FALSE;
16512 *blackPlaysFirst = TRUE;
16518 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16519 /* return the extra info in global variiables */
16521 /* set defaults in case FEN is incomplete */
16522 board[EP_STATUS] = EP_UNKNOWN;
16523 for(i=0; i<nrCastlingRights; i++ ) {
16524 board[CASTLING][i] =
16525 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16526 } /* assume possible unless obviously impossible */
16527 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16528 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16529 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16530 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16531 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16532 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16533 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16534 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16537 while(*p==' ') p++;
16538 if(nrCastlingRights) {
16539 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16540 /* castling indicator present, so default becomes no castlings */
16541 for(i=0; i<nrCastlingRights; i++ ) {
16542 board[CASTLING][i] = NoRights;
16545 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16546 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16547 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16548 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16549 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16551 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16552 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16553 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16555 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16556 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16557 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16558 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16559 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16560 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16563 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16564 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16565 board[CASTLING][2] = whiteKingFile;
16568 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16569 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16570 board[CASTLING][2] = whiteKingFile;
16573 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16574 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16575 board[CASTLING][5] = blackKingFile;
16578 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16579 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16580 board[CASTLING][5] = blackKingFile;
16583 default: /* FRC castlings */
16584 if(c >= 'a') { /* black rights */
16585 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16586 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16587 if(i == BOARD_RGHT) break;
16588 board[CASTLING][5] = i;
16590 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16591 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16593 board[CASTLING][3] = c;
16595 board[CASTLING][4] = c;
16596 } else { /* white rights */
16597 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16598 if(board[0][i] == WhiteKing) break;
16599 if(i == BOARD_RGHT) break;
16600 board[CASTLING][2] = i;
16601 c -= AAA - 'a' + 'A';
16602 if(board[0][c] >= WhiteKing) break;
16604 board[CASTLING][0] = c;
16606 board[CASTLING][1] = c;
16610 for(i=0; i<nrCastlingRights; i++)
16611 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16612 if (appData.debugMode) {
16613 fprintf(debugFP, "FEN castling rights:");
16614 for(i=0; i<nrCastlingRights; i++)
16615 fprintf(debugFP, " %d", board[CASTLING][i]);
16616 fprintf(debugFP, "\n");
16619 while(*p==' ') p++;
16622 /* read e.p. field in games that know e.p. capture */
16623 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16624 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16626 p++; board[EP_STATUS] = EP_NONE;
16628 char c = *p++ - AAA;
16630 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16631 if(*p >= '0' && *p <='9') p++;
16632 board[EP_STATUS] = c;
16637 if(sscanf(p, "%d", &i) == 1) {
16638 FENrulePlies = i; /* 50-move ply counter */
16639 /* (The move number is still ignored) */
16646 EditPositionPasteFEN (char *fen)
16649 Board initial_position;
16651 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16652 DisplayError(_("Bad FEN position in clipboard"), 0);
16655 int savedBlackPlaysFirst = blackPlaysFirst;
16656 EditPositionEvent();
16657 blackPlaysFirst = savedBlackPlaysFirst;
16658 CopyBoard(boards[0], initial_position);
16659 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16660 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16661 DisplayBothClocks();
16662 DrawPosition(FALSE, boards[currentMove]);
16667 static char cseq[12] = "\\ ";
16670 set_cont_sequence (char *new_seq)
16675 // handle bad attempts to set the sequence
16677 return 0; // acceptable error - no debug
16679 len = strlen(new_seq);
16680 ret = (len > 0) && (len < sizeof(cseq));
16682 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16683 else if (appData.debugMode)
16684 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16689 reformat a source message so words don't cross the width boundary. internal
16690 newlines are not removed. returns the wrapped size (no null character unless
16691 included in source message). If dest is NULL, only calculate the size required
16692 for the dest buffer. lp argument indicats line position upon entry, and it's
16693 passed back upon exit.
16696 wrap (char *dest, char *src, int count, int width, int *lp)
16698 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16700 cseq_len = strlen(cseq);
16701 old_line = line = *lp;
16702 ansi = len = clen = 0;
16704 for (i=0; i < count; i++)
16706 if (src[i] == '\033')
16709 // if we hit the width, back up
16710 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16712 // store i & len in case the word is too long
16713 old_i = i, old_len = len;
16715 // find the end of the last word
16716 while (i && src[i] != ' ' && src[i] != '\n')
16722 // word too long? restore i & len before splitting it
16723 if ((old_i-i+clen) >= width)
16730 if (i && src[i-1] == ' ')
16733 if (src[i] != ' ' && src[i] != '\n')
16740 // now append the newline and continuation sequence
16745 strncpy(dest+len, cseq, cseq_len);
16753 dest[len] = src[i];
16757 if (src[i] == '\n')
16762 if (dest && appData.debugMode)
16764 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16765 count, width, line, len, *lp);
16766 show_bytes(debugFP, src, count);
16767 fprintf(debugFP, "\ndest: ");
16768 show_bytes(debugFP, dest, len);
16769 fprintf(debugFP, "\n");
16771 *lp = dest ? line : old_line;
16776 // [HGM] vari: routines for shelving variations
16777 Boolean modeRestore = FALSE;
16780 PushInner (int firstMove, int lastMove)
16782 int i, j, nrMoves = lastMove - firstMove;
16784 // push current tail of game on stack
16785 savedResult[storedGames] = gameInfo.result;
16786 savedDetails[storedGames] = gameInfo.resultDetails;
16787 gameInfo.resultDetails = NULL;
16788 savedFirst[storedGames] = firstMove;
16789 savedLast [storedGames] = lastMove;
16790 savedFramePtr[storedGames] = framePtr;
16791 framePtr -= nrMoves; // reserve space for the boards
16792 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16793 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16794 for(j=0; j<MOVE_LEN; j++)
16795 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16796 for(j=0; j<2*MOVE_LEN; j++)
16797 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16798 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16799 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16800 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16801 pvInfoList[firstMove+i-1].depth = 0;
16802 commentList[framePtr+i] = commentList[firstMove+i];
16803 commentList[firstMove+i] = NULL;
16807 forwardMostMove = firstMove; // truncate game so we can start variation
16811 PushTail (int firstMove, int lastMove)
16813 if(appData.icsActive) { // only in local mode
16814 forwardMostMove = currentMove; // mimic old ICS behavior
16817 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16819 PushInner(firstMove, lastMove);
16820 if(storedGames == 1) GreyRevert(FALSE);
16821 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16825 PopInner (Boolean annotate)
16828 char buf[8000], moveBuf[20];
16830 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16831 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16832 nrMoves = savedLast[storedGames] - currentMove;
16835 if(!WhiteOnMove(currentMove))
16836 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16837 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16838 for(i=currentMove; i<forwardMostMove; i++) {
16840 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16841 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16842 strcat(buf, moveBuf);
16843 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16844 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16848 for(i=1; i<=nrMoves; i++) { // copy last variation back
16849 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16850 for(j=0; j<MOVE_LEN; j++)
16851 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16852 for(j=0; j<2*MOVE_LEN; j++)
16853 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16854 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16855 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16856 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16857 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16858 commentList[currentMove+i] = commentList[framePtr+i];
16859 commentList[framePtr+i] = NULL;
16861 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16862 framePtr = savedFramePtr[storedGames];
16863 gameInfo.result = savedResult[storedGames];
16864 if(gameInfo.resultDetails != NULL) {
16865 free(gameInfo.resultDetails);
16867 gameInfo.resultDetails = savedDetails[storedGames];
16868 forwardMostMove = currentMove + nrMoves;
16872 PopTail (Boolean annotate)
16874 if(appData.icsActive) return FALSE; // only in local mode
16875 if(!storedGames) return FALSE; // sanity
16876 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16878 PopInner(annotate);
16879 if(currentMove < forwardMostMove) ForwardEvent(); else
16880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16882 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16888 { // remove all shelved variations
16890 for(i=0; i<storedGames; i++) {
16891 if(savedDetails[i])
16892 free(savedDetails[i]);
16893 savedDetails[i] = NULL;
16895 for(i=framePtr; i<MAX_MOVES; i++) {
16896 if(commentList[i]) free(commentList[i]);
16897 commentList[i] = NULL;
16899 framePtr = MAX_MOVES-1;
16904 LoadVariation (int index, char *text)
16905 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16906 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16907 int level = 0, move;
16909 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16910 // first find outermost bracketing variation
16911 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16912 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16913 if(*p == '{') wait = '}'; else
16914 if(*p == '[') wait = ']'; else
16915 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16916 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16918 if(*p == wait) wait = NULLCHAR; // closing ]} found
16921 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16922 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16923 end[1] = NULLCHAR; // clip off comment beyond variation
16924 ToNrEvent(currentMove-1);
16925 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16926 // kludge: use ParsePV() to append variation to game
16927 move = currentMove;
16928 ParsePV(start, TRUE, TRUE);
16929 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16930 ClearPremoveHighlights();
16932 ToNrEvent(currentMove+1);