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 ParseArgsFromString(buf);
885 ReplaceEngine(cps, i);
889 while(q = strchr(p, SLASH)) p = q+1;
890 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891 if(engineDir[0] != NULLCHAR)
892 appData.directory[i] = engineDir;
893 else if(p != engineName) { // derive directory from engine path, when not given
895 appData.directory[i] = strdup(engineName);
897 } else appData.directory[i] = ".";
899 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900 snprintf(command, MSG_SIZ, "%s %s", p, params);
903 appData.chessProgram[i] = strdup(p);
904 appData.isUCI[i] = isUCI;
905 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906 appData.hasOwnBookUCI[i] = hasBook;
907 if(!nickName[0]) useNick = FALSE;
908 if(useNick) ASSIGN(appData.pgnName[i], nickName);
912 q = firstChessProgramNames;
913 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916 quote, p, quote, appData.directory[i],
917 useNick ? " -fn \"" : "",
918 useNick ? nickName : "",
920 v1 ? " -firstProtocolVersion 1" : "",
921 hasBook ? "" : " -fNoOwnBookUCI",
922 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923 storeVariant ? " -variant " : "",
924 storeVariant ? VariantName(gameInfo.variant) : "");
925 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
929 ReplaceEngine(cps, i);
935 int matched, min, sec;
937 * Parse timeControl resource
939 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940 appData.movesPerSession)) {
942 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943 DisplayFatalError(buf, 0, 2);
947 * Parse searchTime resource
949 if (*appData.searchTime != NULLCHAR) {
950 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952 searchTime = min * 60;
953 } else if (matched == 2) {
954 searchTime = min * 60 + sec;
957 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958 DisplayFatalError(buf, 0, 2);
967 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970 GetTimeMark(&programStartTime);
971 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972 appData.seedBase = random() + (random()<<15);
973 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976 programStats.ok_to_send = 1;
977 programStats.seen_stat = 0;
980 * Initialize game list
986 * Internet chess server status
988 if (appData.icsActive) {
989 appData.matchMode = FALSE;
990 appData.matchGames = 0;
992 appData.noChessProgram = !appData.zippyPlay;
994 appData.zippyPlay = FALSE;
995 appData.zippyTalk = FALSE;
996 appData.noChessProgram = TRUE;
998 if (*appData.icsHelper != NULLCHAR) {
999 appData.useTelnet = TRUE;
1000 appData.telnetProgram = appData.icsHelper;
1003 appData.zippyTalk = appData.zippyPlay = FALSE;
1006 /* [AS] Initialize pv info list [HGM] and game state */
1010 for( i=0; i<=framePtr; i++ ) {
1011 pvInfoList[i].depth = -1;
1012 boards[i][EP_STATUS] = EP_NONE;
1013 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1019 /* [AS] Adjudication threshold */
1020 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022 InitEngine(&first, 0);
1023 InitEngine(&second, 1);
1026 pairing.which = "pairing"; // pairing engine
1027 pairing.pr = NoProc;
1029 pairing.program = appData.pairingEngine;
1030 pairing.host = "localhost";
1033 if (appData.icsActive) {
1034 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1035 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036 appData.clockMode = FALSE;
1037 first.sendTime = second.sendTime = 0;
1041 /* Override some settings from environment variables, for backward
1042 compatibility. Unfortunately it's not feasible to have the env
1043 vars just set defaults, at least in xboard. Ugh.
1045 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1050 if (!appData.icsActive) {
1054 /* Check for variants that are supported only in ICS mode,
1055 or not at all. Some that are accepted here nevertheless
1056 have bugs; see comments below.
1058 VariantClass variant = StringToVariant(appData.variant);
1060 case VariantBughouse: /* need four players and two boards */
1061 case VariantKriegspiel: /* need to hide pieces and move details */
1062 /* case VariantFischeRandom: (Fabien: moved below) */
1063 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064 if( (len >= MSG_SIZ) && appData.debugMode )
1065 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067 DisplayFatalError(buf, 0, 2);
1070 case VariantUnknown:
1071 case VariantLoadable:
1081 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082 if( (len >= MSG_SIZ) && appData.debugMode )
1083 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085 DisplayFatalError(buf, 0, 2);
1088 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1089 case VariantFairy: /* [HGM] TestLegality definitely off! */
1090 case VariantGothic: /* [HGM] should work */
1091 case VariantCapablanca: /* [HGM] should work */
1092 case VariantCourier: /* [HGM] initial forced moves not implemented */
1093 case VariantShogi: /* [HGM] could still mate with pawn drop */
1094 case VariantKnightmate: /* [HGM] should work */
1095 case VariantCylinder: /* [HGM] untested */
1096 case VariantFalcon: /* [HGM] untested */
1097 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098 offboard interposition not understood */
1099 case VariantNormal: /* definitely works! */
1100 case VariantWildCastle: /* pieces not automatically shuffled */
1101 case VariantNoCastle: /* pieces not automatically shuffled */
1102 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103 case VariantLosers: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantSuicide: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantGiveaway: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantTwoKings: /* should work */
1110 case VariantAtomic: /* should work except for win condition */
1111 case Variant3Check: /* should work except for win condition */
1112 case VariantShatranj: /* should work except for all win conditions */
1113 case VariantMakruk: /* should work except for draw countdown */
1114 case VariantBerolina: /* might work if TestLegality is off */
1115 case VariantCapaRandom: /* should work */
1116 case VariantJanus: /* should work */
1117 case VariantSuper: /* experimental */
1118 case VariantGreat: /* experimental, requires legality testing to be off */
1119 case VariantSChess: /* S-Chess, should work */
1120 case VariantGrand: /* should work */
1121 case VariantSpartan: /* should work */
1129 NextIntegerFromString (char ** str, long * value)
1134 while( *s == ' ' || *s == '\t' ) {
1140 if( *s >= '0' && *s <= '9' ) {
1141 while( *s >= '0' && *s <= '9' ) {
1142 *value = *value * 10 + (*s - '0');
1155 NextTimeControlFromString (char ** str, long * value)
1158 int result = NextIntegerFromString( str, &temp );
1161 *value = temp * 60; /* Minutes */
1162 if( **str == ':' ) {
1164 result = NextIntegerFromString( str, &temp );
1165 *value += temp; /* Seconds */
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175 int result = -1, type = 0; long temp, temp2;
1177 if(**str != ':') return -1; // old params remain in force!
1179 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180 if( NextIntegerFromString( str, &temp ) ) return -1;
1181 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1184 /* time only: incremental or sudden-death time control */
1185 if(**str == '+') { /* increment follows; read it */
1187 if(**str == '!') type = *(*str)++; // Bronstein TC
1188 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189 *inc = temp2 * 1000;
1190 if(**str == '.') { // read fraction of increment
1191 char *start = ++(*str);
1192 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194 while(start++ < *str) temp2 /= 10;
1198 *moves = 0; *tc = temp * 1000; *incType = type;
1202 (*str)++; /* classical time control */
1203 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 { /* [HGM] get time to add from the multi-session time-control string */
1217 int incType, moves=1; /* kludge to force reading of first session */
1218 long time, increment;
1221 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227 if(movenr == -1) return time; /* last move before new session */
1228 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230 if(!moves) return increment; /* current session is incremental */
1231 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232 } while(movenr >= -1); /* try again for next session */
1234 return 0; // no new time quota on this move
1238 ParseTimeControl (char *tc, float ti, int mps)
1242 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1245 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1251 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1256 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258 snprintf(buf, MSG_SIZ, ":%s", mytc);
1260 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1267 /* Parse second time control */
1270 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1278 timeControl_2 = tc2 * 1000;
1288 timeControl = tc1 * 1000;
1291 timeIncrement = ti * 1000; /* convert to ms */
1292 movesPerSession = 0;
1295 movesPerSession = mps;
1303 if (appData.debugMode) {
1304 fprintf(debugFP, "%s\n", programVersion);
1307 set_cont_sequence(appData.wrapContSeq);
1308 if (appData.matchGames > 0) {
1309 appData.matchMode = TRUE;
1310 } else if (appData.matchMode) {
1311 appData.matchGames = 1;
1313 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314 appData.matchGames = appData.sameColorGames;
1315 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1320 if (appData.noChessProgram || first.protocolVersion == 1) {
1323 /* kludge: allow timeout for initial "feature" commands */
1325 DisplayMessage("", _("Starting chess program"));
1326 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1331 CalculateIndex (int index, int gameNr)
1332 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334 if(index > 0) return index; // fixed nmber
1335 if(index == 0) return 1;
1336 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1342 LoadGameOrPosition (int gameNr)
1343 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344 if (*appData.loadGameFile != NULLCHAR) {
1345 if (!LoadGameFromFile(appData.loadGameFile,
1346 CalculateIndex(appData.loadGameIndex, gameNr),
1347 appData.loadGameFile, FALSE)) {
1348 DisplayFatalError(_("Bad game file"), 0, 1);
1351 } else if (*appData.loadPositionFile != NULLCHAR) {
1352 if (!LoadPositionFromFile(appData.loadPositionFile,
1353 CalculateIndex(appData.loadPositionIndex, gameNr),
1354 appData.loadPositionFile)) {
1355 DisplayFatalError(_("Bad position file"), 0, 1);
1363 ReserveGame (int gameNr, char resChar)
1365 FILE *tf = fopen(appData.tourneyFile, "r+");
1366 char *p, *q, c, buf[MSG_SIZ];
1367 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368 safeStrCpy(buf, lastMsg, MSG_SIZ);
1369 DisplayMessage(_("Pick new game"), "");
1370 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371 ParseArgsFromFile(tf);
1372 p = q = appData.results;
1373 if(appData.debugMode) {
1374 char *r = appData.participants;
1375 fprintf(debugFP, "results = '%s'\n", p);
1376 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377 fprintf(debugFP, "\n");
1379 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382 safeStrCpy(q, p, strlen(p) + 2);
1383 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1389 fseek(tf, -(strlen(p)+4), SEEK_END);
1391 if(c != '"') // depending on DOS or Unix line endings we can be one off
1392 fseek(tf, -(strlen(p)+2), SEEK_END);
1393 else fseek(tf, -(strlen(p)+3), SEEK_END);
1394 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395 DisplayMessage(buf, "");
1396 free(p); appData.results = q;
1397 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399 UnloadEngine(&first); // next game belongs to other pairing;
1400 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1405 MatchEvent (int mode)
1406 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1408 if(matchMode) { // already in match mode: switch it off
1410 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1413 // if(gameMode != BeginningOfGame) {
1414 // DisplayError(_("You can only start a match from the initial position."), 0);
1418 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419 /* Set up machine vs. machine match */
1421 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422 if(appData.tourneyFile[0]) {
1424 if(nextGame > appData.matchGames) {
1426 if(strchr(appData.results, '*') == NULL) {
1428 appData.tourneyCycles++;
1429 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1431 NextTourneyGame(-1, &dummy);
1433 if(nextGame <= appData.matchGames) {
1434 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1436 ScheduleDelayedEvent(NextMatchGame, 10000);
1441 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442 DisplayError(buf, 0);
1443 appData.tourneyFile[0] = 0;
1447 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1448 DisplayFatalError(_("Can't have a match with no chess programs"),
1453 matchGame = roundNr = 1;
1454 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1459 InitBackEnd3 P((void))
1461 GameMode initialMode;
1465 InitChessProgram(&first, startedFromSetupPosition);
1467 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1468 free(programVersion);
1469 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1473 if (appData.icsActive) {
1475 /* [DM] Make a console window if needed [HGM] merged ifs */
1481 if (*appData.icsCommPort != NULLCHAR)
1482 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483 appData.icsCommPort);
1485 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486 appData.icsHost, appData.icsPort);
1488 if( (len >= MSG_SIZ) && appData.debugMode )
1489 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1491 DisplayFatalError(buf, err, 1);
1496 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1498 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501 } else if (appData.noChessProgram) {
1507 if (*appData.cmailGameName != NULLCHAR) {
1509 OpenLoopback(&cmailPR);
1511 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1515 DisplayMessage("", "");
1516 if (StrCaseCmp(appData.initialMode, "") == 0) {
1517 initialMode = BeginningOfGame;
1518 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1524 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525 initialMode = TwoMachinesPlay;
1526 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527 initialMode = AnalyzeFile;
1528 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529 initialMode = AnalyzeMode;
1530 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531 initialMode = MachinePlaysWhite;
1532 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533 initialMode = MachinePlaysBlack;
1534 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535 initialMode = EditGame;
1536 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537 initialMode = EditPosition;
1538 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539 initialMode = Training;
1541 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542 if( (len >= MSG_SIZ) && appData.debugMode )
1543 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1545 DisplayFatalError(buf, 0, 2);
1549 if (appData.matchMode) {
1550 if(appData.tourneyFile[0]) { // start tourney from command line
1552 if(f = fopen(appData.tourneyFile, "r")) {
1553 ParseArgsFromFile(f); // make sure tourney parmeters re known
1555 appData.clockMode = TRUE;
1557 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1560 } else if (*appData.cmailGameName != NULLCHAR) {
1561 /* Set up cmail mode */
1562 ReloadCmailMsgEvent(TRUE);
1564 /* Set up other modes */
1565 if (initialMode == AnalyzeFile) {
1566 if (*appData.loadGameFile == NULLCHAR) {
1567 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1571 if (*appData.loadGameFile != NULLCHAR) {
1572 (void) LoadGameFromFile(appData.loadGameFile,
1573 appData.loadGameIndex,
1574 appData.loadGameFile, TRUE);
1575 } else if (*appData.loadPositionFile != NULLCHAR) {
1576 (void) LoadPositionFromFile(appData.loadPositionFile,
1577 appData.loadPositionIndex,
1578 appData.loadPositionFile);
1579 /* [HGM] try to make self-starting even after FEN load */
1580 /* to allow automatic setup of fairy variants with wtm */
1581 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582 gameMode = BeginningOfGame;
1583 setboardSpoiledMachineBlack = 1;
1585 /* [HGM] loadPos: make that every new game uses the setup */
1586 /* from file as long as we do not switch variant */
1587 if(!blackPlaysFirst) {
1588 startedFromPositionFile = TRUE;
1589 CopyBoard(filePosition, boards[0]);
1592 if (initialMode == AnalyzeMode) {
1593 if (appData.noChessProgram) {
1594 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1597 if (appData.icsActive) {
1598 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1602 } else if (initialMode == AnalyzeFile) {
1603 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604 ShowThinkingEvent();
1606 AnalysisPeriodicEvent(1);
1607 } else if (initialMode == MachinePlaysWhite) {
1608 if (appData.noChessProgram) {
1609 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1613 if (appData.icsActive) {
1614 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1618 MachineWhiteEvent();
1619 } else if (initialMode == MachinePlaysBlack) {
1620 if (appData.noChessProgram) {
1621 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1625 if (appData.icsActive) {
1626 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1630 MachineBlackEvent();
1631 } else if (initialMode == TwoMachinesPlay) {
1632 if (appData.noChessProgram) {
1633 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1637 if (appData.icsActive) {
1638 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1643 } else if (initialMode == EditGame) {
1645 } else if (initialMode == EditPosition) {
1646 EditPositionEvent();
1647 } else if (initialMode == Training) {
1648 if (*appData.loadGameFile == NULLCHAR) {
1649 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1660 DisplayBook(current+1);
1662 MoveHistorySet( movelist, first, last, current, pvInfoList );
1664 EvalGraphSet( first, last, current, pvInfoList );
1666 MakeEngineOutputTitle();
1670 * Establish will establish a contact to a remote host.port.
1671 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672 * used to talk to the host.
1673 * Returns 0 if okay, error code if not.
1680 if (*appData.icsCommPort != NULLCHAR) {
1681 /* Talk to the host through a serial comm port */
1682 return OpenCommPort(appData.icsCommPort, &icsPR);
1684 } else if (*appData.gateway != NULLCHAR) {
1685 if (*appData.remoteShell == NULLCHAR) {
1686 /* Use the rcmd protocol to run telnet program on a gateway host */
1687 snprintf(buf, sizeof(buf), "%s %s %s",
1688 appData.telnetProgram, appData.icsHost, appData.icsPort);
1689 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1692 /* Use the rsh program to run telnet program on a gateway host */
1693 if (*appData.remoteUser == NULLCHAR) {
1694 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695 appData.gateway, appData.telnetProgram,
1696 appData.icsHost, appData.icsPort);
1698 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699 appData.remoteShell, appData.gateway,
1700 appData.remoteUser, appData.telnetProgram,
1701 appData.icsHost, appData.icsPort);
1703 return StartChildProcess(buf, "", &icsPR);
1706 } else if (appData.useTelnet) {
1707 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1710 /* TCP socket interface differs somewhat between
1711 Unix and NT; handle details in the front end.
1713 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1718 EscapeExpand (char *p, char *q)
1719 { // [HGM] initstring: routine to shape up string arguments
1720 while(*p++ = *q++) if(p[-1] == '\\')
1722 case 'n': p[-1] = '\n'; break;
1723 case 'r': p[-1] = '\r'; break;
1724 case 't': p[-1] = '\t'; break;
1725 case '\\': p[-1] = '\\'; break;
1726 case 0: *p = 0; return;
1727 default: p[-1] = q[-1]; break;
1732 show_bytes (FILE *fp, char *buf, int count)
1735 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736 fprintf(fp, "\\%03o", *buf & 0xff);
1745 /* Returns an errno value */
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1749 char buf[8192], *p, *q, *buflim;
1750 int left, newcount, outcount;
1752 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753 *appData.gateway != NULLCHAR) {
1754 if (appData.debugMode) {
1755 fprintf(debugFP, ">ICS: ");
1756 show_bytes(debugFP, message, count);
1757 fprintf(debugFP, "\n");
1759 return OutputToProcess(pr, message, count, outError);
1762 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769 if (appData.debugMode) {
1770 fprintf(debugFP, ">ICS: ");
1771 show_bytes(debugFP, buf, newcount);
1772 fprintf(debugFP, "\n");
1774 outcount = OutputToProcess(pr, buf, newcount, outError);
1775 if (outcount < newcount) return -1; /* to be sure */
1782 } else if (((unsigned char) *p) == TN_IAC) {
1783 *q++ = (char) TN_IAC;
1790 if (appData.debugMode) {
1791 fprintf(debugFP, ">ICS: ");
1792 show_bytes(debugFP, buf, newcount);
1793 fprintf(debugFP, "\n");
1795 outcount = OutputToProcess(pr, buf, newcount, outError);
1796 if (outcount < newcount) return -1; /* to be sure */
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1803 int outError, outCount;
1804 static int gotEof = 0;
1806 /* Pass data read from player on to ICS */
1809 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1813 } else if (count < 0) {
1814 RemoveInputSource(isr);
1815 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816 } else if (gotEof++ > 0) {
1817 RemoveInputSource(isr);
1818 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1824 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827 SendToICS("date\n");
1828 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1831 /* added routine for printf style output to ics */
1833 ics_printf (char *format, ...)
1835 char buffer[MSG_SIZ];
1838 va_start(args, format);
1839 vsnprintf(buffer, sizeof(buffer), format, args);
1840 buffer[sizeof(buffer)-1] = '\0';
1848 int count, outCount, outError;
1850 if (icsPR == NoProc) return;
1853 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854 if (outCount < count) {
1855 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1859 /* This is used for sending logon scripts to the ICS. Sending
1860 without a delay causes problems when using timestamp on ICC
1861 (at least on my machine). */
1863 SendToICSDelayed (char *s, long msdelay)
1865 int count, outCount, outError;
1867 if (icsPR == NoProc) return;
1870 if (appData.debugMode) {
1871 fprintf(debugFP, ">ICS: ");
1872 show_bytes(debugFP, s, count);
1873 fprintf(debugFP, "\n");
1875 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883 /* Remove all highlighting escape sequences in s
1884 Also deletes any suffix starting with '('
1887 StripHighlightAndTitle (char *s)
1889 static char retbuf[MSG_SIZ];
1892 while (*s != NULLCHAR) {
1893 while (*s == '\033') {
1894 while (*s != NULLCHAR && !isalpha(*s)) s++;
1895 if (*s != NULLCHAR) s++;
1897 while (*s != NULLCHAR && *s != '\033') {
1898 if (*s == '(' || *s == '[') {
1909 /* Remove all highlighting escape sequences in s */
1911 StripHighlight (char *s)
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1929 char *variantNames[] = VARIANT_NAMES;
1931 VariantName (VariantClass v)
1933 return variantNames[v];
1937 /* Identify a variant from the strings the chess servers use or the
1938 PGN Variant tag names we use. */
1940 StringToVariant (char *e)
1944 VariantClass v = VariantNormal;
1945 int i, found = FALSE;
1951 /* [HGM] skip over optional board-size prefixes */
1952 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954 while( *e++ != '_');
1957 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1961 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962 if (StrCaseStr(e, variantNames[i])) {
1963 v = (VariantClass) i;
1970 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971 || StrCaseStr(e, "wild/fr")
1972 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973 v = VariantFischeRandom;
1974 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975 (i = 1, p = StrCaseStr(e, "w"))) {
1977 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1984 case 0: /* FICS only, actually */
1986 /* Castling legal even if K starts on d-file */
1987 v = VariantWildCastle;
1992 /* Castling illegal even if K & R happen to start in
1993 normal positions. */
1994 v = VariantNoCastle;
2007 /* Castling legal iff K & R start in normal positions */
2013 /* Special wilds for position setup; unclear what to do here */
2014 v = VariantLoadable;
2017 /* Bizarre ICC game */
2018 v = VariantTwoKings;
2021 v = VariantKriegspiel;
2027 v = VariantFischeRandom;
2030 v = VariantCrazyhouse;
2033 v = VariantBughouse;
2039 /* Not quite the same as FICS suicide! */
2040 v = VariantGiveaway;
2046 v = VariantShatranj;
2049 /* Temporary names for future ICC types. The name *will* change in
2050 the next xboard/WinBoard release after ICC defines it. */
2088 v = VariantCapablanca;
2091 v = VariantKnightmate;
2097 v = VariantCylinder;
2103 v = VariantCapaRandom;
2106 v = VariantBerolina;
2118 /* Found "wild" or "w" in the string but no number;
2119 must assume it's normal chess. */
2123 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124 if( (len >= MSG_SIZ) && appData.debugMode )
2125 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2127 DisplayError(buf, 0);
2133 if (appData.debugMode) {
2134 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135 e, wnum, VariantName(v));
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144 advance *index beyond it, and set leftover_start to the new value of
2145 *index; else return FALSE. If pattern contains the character '*', it
2146 matches any sequence of characters not containing '\r', '\n', or the
2147 character following the '*' (if any), and the matched sequence(s) are
2148 copied into star_match.
2151 looking_at ( char *buf, int *index, char *pattern)
2153 char *bufp = &buf[*index], *patternp = pattern;
2155 char *matchp = star_match[0];
2158 if (*patternp == NULLCHAR) {
2159 *index = leftover_start = bufp - buf;
2163 if (*bufp == NULLCHAR) return FALSE;
2164 if (*patternp == '*') {
2165 if (*bufp == *(patternp + 1)) {
2167 matchp = star_match[++star_count];
2171 } else if (*bufp == '\n' || *bufp == '\r') {
2173 if (*patternp == NULLCHAR)
2178 *matchp++ = *bufp++;
2182 if (*patternp != *bufp) return FALSE;
2189 SendToPlayer (char *data, int length)
2191 int error, outCount;
2192 outCount = OutputToProcess(NoProc, data, length, &error);
2193 if (outCount < length) {
2194 DisplayFatalError(_("Error writing to display"), error, 1);
2199 PackHolding (char packed[], char *holding)
2209 switch (runlength) {
2220 sprintf(q, "%d", runlength);
2232 /* Telnet protocol requests from the front end */
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2236 unsigned char msg[3];
2237 int outCount, outError;
2239 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2241 if (appData.debugMode) {
2242 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2258 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2267 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2270 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2275 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2277 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2284 if (!appData.icsActive) return;
2285 TelnetRequest(TN_DO, TN_ECHO);
2291 if (!appData.icsActive) return;
2292 TelnetRequest(TN_DONT, TN_ECHO);
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2298 /* put the holdings sent to us by the server on the board holdings area */
2299 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2303 if(gameInfo.holdingsWidth < 2) return;
2304 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305 return; // prevent overwriting by pre-board holdings
2307 if( (int)lowestPiece >= BlackPawn ) {
2310 holdingsStartRow = BOARD_HEIGHT-1;
2313 holdingsColumn = BOARD_WIDTH-1;
2314 countsColumn = BOARD_WIDTH-2;
2315 holdingsStartRow = 0;
2319 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320 board[i][holdingsColumn] = EmptySquare;
2321 board[i][countsColumn] = (ChessSquare) 0;
2323 while( (p=*holdings++) != NULLCHAR ) {
2324 piece = CharToPiece( ToUpper(p) );
2325 if(piece == EmptySquare) continue;
2326 /*j = (int) piece - (int) WhitePawn;*/
2327 j = PieceToNumber(piece);
2328 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329 if(j < 0) continue; /* should not happen */
2330 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332 board[holdingsStartRow+j*direction][countsColumn]++;
2338 VariantSwitch (Board board, VariantClass newVariant)
2340 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341 static Board oldBoard;
2343 startedFromPositionFile = FALSE;
2344 if(gameInfo.variant == newVariant) return;
2346 /* [HGM] This routine is called each time an assignment is made to
2347 * gameInfo.variant during a game, to make sure the board sizes
2348 * are set to match the new variant. If that means adding or deleting
2349 * holdings, we shift the playing board accordingly
2350 * This kludge is needed because in ICS observe mode, we get boards
2351 * of an ongoing game without knowing the variant, and learn about the
2352 * latter only later. This can be because of the move list we requested,
2353 * in which case the game history is refilled from the beginning anyway,
2354 * but also when receiving holdings of a crazyhouse game. In the latter
2355 * case we want to add those holdings to the already received position.
2359 if (appData.debugMode) {
2360 fprintf(debugFP, "Switch board from %s to %s\n",
2361 VariantName(gameInfo.variant), VariantName(newVariant));
2362 setbuf(debugFP, NULL);
2364 shuffleOpenings = 0; /* [HGM] shuffle */
2365 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2369 newWidth = 9; newHeight = 9;
2370 gameInfo.holdingsSize = 7;
2371 case VariantBughouse:
2372 case VariantCrazyhouse:
2373 newHoldingsWidth = 2; break;
2377 newHoldingsWidth = 2;
2378 gameInfo.holdingsSize = 8;
2381 case VariantCapablanca:
2382 case VariantCapaRandom:
2385 newHoldingsWidth = gameInfo.holdingsSize = 0;
2388 if(newWidth != gameInfo.boardWidth ||
2389 newHeight != gameInfo.boardHeight ||
2390 newHoldingsWidth != gameInfo.holdingsWidth ) {
2392 /* shift position to new playing area, if needed */
2393 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394 for(i=0; i<BOARD_HEIGHT; i++)
2395 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2398 for(i=0; i<newHeight; i++) {
2399 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2402 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403 for(i=0; i<BOARD_HEIGHT; i++)
2404 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408 gameInfo.boardWidth = newWidth;
2409 gameInfo.boardHeight = newHeight;
2410 gameInfo.holdingsWidth = newHoldingsWidth;
2411 gameInfo.variant = newVariant;
2412 InitDrawingSizes(-2, 0);
2413 } else gameInfo.variant = newVariant;
2414 CopyBoard(oldBoard, board); // remember correctly formatted board
2415 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2416 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2419 static int loggedOn = FALSE;
2421 /*-- Game start info cache: --*/
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\ ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2451 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453 if(r < minRating+100 && r >=0 ) r = minRating+100;
2454 if(r > maxRating) r = maxRating;
2455 if(tc < 1.) tc = 1.;
2456 if(tc > 95.) tc = 95.;
2457 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458 y = ((double)r - minRating)/(maxRating - minRating)
2459 * (h-vMargin-squareSize/8-1) + vMargin;
2460 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461 if(strstr(seekAdList[i], " u ")) color = 1;
2462 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463 !strstr(seekAdList[i], "bullet") &&
2464 !strstr(seekAdList[i], "blitz") &&
2465 !strstr(seekAdList[i], "standard") ) color = 2;
2466 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2471 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2473 char buf[MSG_SIZ], *ext = "";
2474 VariantClass v = StringToVariant(type);
2475 if(strstr(type, "wild")) {
2476 ext = type + 4; // append wild number
2477 if(v == VariantFischeRandom) type = "chess960"; else
2478 if(v == VariantLoadable) type = "setup"; else
2479 type = VariantName(v);
2481 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487 seekNrList[nrOfSeekAds] = nr;
2488 zList[nrOfSeekAds] = 0;
2489 seekAdList[nrOfSeekAds++] = StrSave(buf);
2490 if(plot) PlotSeekAd(nrOfSeekAds-1);
2495 EraseSeekDot (int i)
2497 int x = xList[i], y = yList[i], d=squareSize/4, k;
2498 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500 // now replot every dot that overlapped
2501 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502 int xx = xList[k], yy = yList[k];
2503 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504 DrawSeekDot(xx, yy, colorList[k]);
2509 RemoveSeekAd (int nr)
2512 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2514 if(seekAdList[i]) free(seekAdList[i]);
2515 seekAdList[i] = seekAdList[--nrOfSeekAds];
2516 seekNrList[i] = seekNrList[nrOfSeekAds];
2517 ratingList[i] = ratingList[nrOfSeekAds];
2518 colorList[i] = colorList[nrOfSeekAds];
2519 tcList[i] = tcList[nrOfSeekAds];
2520 xList[i] = xList[nrOfSeekAds];
2521 yList[i] = yList[nrOfSeekAds];
2522 zList[i] = zList[nrOfSeekAds];
2523 seekAdList[nrOfSeekAds] = NULL;
2529 MatchSoughtLine (char *line)
2531 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532 int nr, base, inc, u=0; char dummy;
2534 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2537 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2539 // match: compact and save the line
2540 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2550 if(!seekGraphUp) return FALSE;
2551 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2554 DrawSeekBackground(0, 0, w, h);
2555 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2560 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2563 snprintf(buf, MSG_SIZ, "%d", i);
2564 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2567 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568 for(i=1; i<100; i+=(i<10?1:5)) {
2569 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2573 snprintf(buf, MSG_SIZ, "%d", i);
2574 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2577 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2584 static int lastDown = 0, displayed = 0, lastSecond;
2585 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586 if(click == Release || moving) return FALSE;
2588 soughtPending = TRUE;
2589 SendToICS(ics_prefix);
2590 SendToICS("sought\n"); // should this be "sought all"?
2591 } else { // issue challenge based on clicked ad
2592 int dist = 10000; int i, closest = 0, second = 0;
2593 for(i=0; i<nrOfSeekAds; i++) {
2594 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2595 if(d < dist) { dist = d; closest = i; }
2596 second += (d - zList[i] < 120); // count in-range ads
2597 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2601 second = (second > 1);
2602 if(displayed != closest || second != lastSecond) {
2603 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604 lastSecond = second; displayed = closest;
2606 if(click == Press) {
2607 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2610 } // on press 'hit', only show info
2611 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613 SendToICS(ics_prefix);
2615 return TRUE; // let incoming board of started game pop down the graph
2616 } else if(click == Release) { // release 'miss' is ignored
2617 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618 if(moving == 2) { // right up-click
2619 nrOfSeekAds = 0; // refresh graph
2620 soughtPending = TRUE;
2621 SendToICS(ics_prefix);
2622 SendToICS("sought\n"); // should this be "sought all"?
2625 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626 // press miss or release hit 'pop down' seek graph
2627 seekGraphUp = FALSE;
2628 DrawPosition(TRUE, NULL);
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2646 static int started = STARTED_NONE;
2647 static char parse[20000];
2648 static int parse_pos = 0;
2649 static char buf[BUF_SIZE + 1];
2650 static int firstTime = TRUE, intfSet = FALSE;
2651 static ColorClass prevColor = ColorNormal;
2652 static int savingComment = FALSE;
2653 static int cmatch = 0; // continuation sequence match
2660 int backup; /* [DM] For zippy color lines */
2662 char talker[MSG_SIZ]; // [HGM] chat
2665 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2667 if (appData.debugMode) {
2669 fprintf(debugFP, "<ICS: ");
2670 show_bytes(debugFP, data, count);
2671 fprintf(debugFP, "\n");
2675 if (appData.debugMode) { int f = forwardMostMove;
2676 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2681 /* If last read ended with a partial line that we couldn't parse,
2682 prepend it to the new read and try again. */
2683 if (leftover_len > 0) {
2684 for (i=0; i<leftover_len; i++)
2685 buf[i] = buf[leftover_start + i];
2688 /* copy new characters into the buffer */
2689 bp = buf + leftover_len;
2690 buf_len=leftover_len;
2691 for (i=0; i<count; i++)
2694 if (data[i] == '\r')
2697 // join lines split by ICS?
2698 if (!appData.noJoin)
2701 Joining just consists of finding matches against the
2702 continuation sequence, and discarding that sequence
2703 if found instead of copying it. So, until a match
2704 fails, there's nothing to do since it might be the
2705 complete sequence, and thus, something we don't want
2708 if (data[i] == cont_seq[cmatch])
2711 if (cmatch == strlen(cont_seq))
2713 cmatch = 0; // complete match. just reset the counter
2716 it's possible for the ICS to not include the space
2717 at the end of the last word, making our [correct]
2718 join operation fuse two separate words. the server
2719 does this when the space occurs at the width setting.
2721 if (!buf_len || buf[buf_len-1] != ' ')
2732 match failed, so we have to copy what matched before
2733 falling through and copying this character. In reality,
2734 this will only ever be just the newline character, but
2735 it doesn't hurt to be precise.
2737 strncpy(bp, cont_seq, cmatch);
2749 buf[buf_len] = NULLCHAR;
2750 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2755 while (i < buf_len) {
2756 /* Deal with part of the TELNET option negotiation
2757 protocol. We refuse to do anything beyond the
2758 defaults, except that we allow the WILL ECHO option,
2759 which ICS uses to turn off password echoing when we are
2760 directly connected to it. We reject this option
2761 if localLineEditing mode is on (always on in xboard)
2762 and we are talking to port 23, which might be a real
2763 telnet server that will try to keep WILL ECHO on permanently.
2765 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767 unsigned char option;
2769 switch ((unsigned char) buf[++i]) {
2771 if (appData.debugMode)
2772 fprintf(debugFP, "\n<WILL ");
2773 switch (option = (unsigned char) buf[++i]) {
2775 if (appData.debugMode)
2776 fprintf(debugFP, "ECHO ");
2777 /* Reply only if this is a change, according
2778 to the protocol rules. */
2779 if (remoteEchoOption) break;
2780 if (appData.localLineEditing &&
2781 atoi(appData.icsPort) == TN_PORT) {
2782 TelnetRequest(TN_DONT, TN_ECHO);
2785 TelnetRequest(TN_DO, TN_ECHO);
2786 remoteEchoOption = TRUE;
2790 if (appData.debugMode)
2791 fprintf(debugFP, "%d ", option);
2792 /* Whatever this is, we don't want it. */
2793 TelnetRequest(TN_DONT, option);
2798 if (appData.debugMode)
2799 fprintf(debugFP, "\n<WONT ");
2800 switch (option = (unsigned char) buf[++i]) {
2802 if (appData.debugMode)
2803 fprintf(debugFP, "ECHO ");
2804 /* Reply only if this is a change, according
2805 to the protocol rules. */
2806 if (!remoteEchoOption) break;
2808 TelnetRequest(TN_DONT, TN_ECHO);
2809 remoteEchoOption = FALSE;
2812 if (appData.debugMode)
2813 fprintf(debugFP, "%d ", (unsigned char) option);
2814 /* Whatever this is, it must already be turned
2815 off, because we never agree to turn on
2816 anything non-default, so according to the
2817 protocol rules, we don't reply. */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<DO ");
2824 switch (option = (unsigned char) buf[++i]) {
2826 /* Whatever this is, we refuse to do it. */
2827 if (appData.debugMode)
2828 fprintf(debugFP, "%d ", option);
2829 TelnetRequest(TN_WONT, option);
2834 if (appData.debugMode)
2835 fprintf(debugFP, "\n<DONT ");
2836 switch (option = (unsigned char) buf[++i]) {
2838 if (appData.debugMode)
2839 fprintf(debugFP, "%d ", option);
2840 /* Whatever this is, we are already not doing
2841 it, because we never agree to do anything
2842 non-default, so according to the protocol
2843 rules, we don't reply. */
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<IAC ");
2850 /* Doubled IAC; pass it through */
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856 /* Drop all other telnet commands on the floor */
2859 if (oldi > next_out)
2860 SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* OK, this at least will *usually* work */
2867 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2871 if (loggedOn && !intfSet) {
2872 if (ics_type == ICS_ICC) {
2873 snprintf(str, MSG_SIZ,
2874 "/set-quietly interface %s\n/set-quietly style 12\n",
2876 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877 strcat(str, "/set-2 51 1\n/set seek 1\n");
2878 } else if (ics_type == ICS_CHESSNET) {
2879 snprintf(str, MSG_SIZ, "/style 12\n");
2881 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882 strcat(str, programVersion);
2883 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2887 strcat(str, "$iset nohighlight 1\n");
2889 strcat(str, "$iset lock 1\n$style 12\n");
2892 NotifyFrontendLogin();
2896 if (started == STARTED_COMMENT) {
2897 /* Accumulate characters in comment */
2898 parse[parse_pos++] = buf[i];
2899 if (buf[i] == '\n') {
2900 parse[parse_pos] = NULLCHAR;
2901 if(chattingPartner>=0) {
2903 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904 OutputChatMessage(chattingPartner, mess);
2905 chattingPartner = -1;
2906 next_out = i+1; // [HGM] suppress printing in ICS window
2908 if(!suppressKibitz) // [HGM] kibitz
2909 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911 int nrDigit = 0, nrAlph = 0, j;
2912 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914 parse[parse_pos] = NULLCHAR;
2915 // try to be smart: if it does not look like search info, it should go to
2916 // ICS interaction window after all, not to engine-output window.
2917 for(j=0; j<parse_pos; j++) { // count letters and digits
2918 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2920 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2922 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923 int depth=0; float score;
2924 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926 pvInfoList[forwardMostMove-1].depth = depth;
2927 pvInfoList[forwardMostMove-1].score = 100*score;
2929 OutputKibitz(suppressKibitz, parse);
2932 if(gameMode == IcsObserving) // restore original ICS messages
2933 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2935 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936 SendToPlayer(tmp, strlen(tmp));
2938 next_out = i+1; // [HGM] suppress printing in ICS window
2940 started = STARTED_NONE;
2942 /* Don't match patterns against characters in comment */
2947 if (started == STARTED_CHATTER) {
2948 if (buf[i] != '\n') {
2949 /* Don't match patterns against characters in chatter */
2953 started = STARTED_NONE;
2954 if(suppressKibitz) next_out = i+1;
2957 /* Kludge to deal with rcmd protocol */
2958 if (firstTime && looking_at(buf, &i, "\001*")) {
2959 DisplayFatalError(&buf[1], 0, 1);
2965 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2968 if (appData.debugMode)
2969 fprintf(debugFP, "ics_type %d\n", ics_type);
2972 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973 ics_type = ICS_FICS;
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ics_type %d\n", ics_type);
2979 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980 ics_type = ICS_CHESSNET;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2988 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989 looking_at(buf, &i, "Logging you in as \"*\"") ||
2990 looking_at(buf, &i, "will be \"*\""))) {
2991 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2995 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2997 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998 DisplayIcsInteractionTitle(buf);
2999 have_set_title = TRUE;
3002 /* skip finger notes */
3003 if (started == STARTED_NONE &&
3004 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005 (buf[i] == '1' && buf[i+1] == '0')) &&
3006 buf[i+2] == ':' && buf[i+3] == ' ') {
3007 started = STARTED_CHATTER;
3013 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014 if(appData.seekGraph) {
3015 if(soughtPending && MatchSoughtLine(buf+i)) {
3016 i = strstr(buf+i, "rated") - buf;
3017 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018 next_out = leftover_start = i;
3019 started = STARTED_CHATTER;
3020 suppressKibitz = TRUE;
3023 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024 && looking_at(buf, &i, "* ads displayed")) {
3025 soughtPending = FALSE;
3030 if(appData.autoRefresh) {
3031 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032 int s = (ics_type == ICS_ICC); // ICC format differs
3034 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036 looking_at(buf, &i, "*% "); // eat prompt
3037 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039 next_out = i; // suppress
3042 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043 char *p = star_match[0];
3045 if(seekGraphUp) RemoveSeekAd(atoi(p));
3046 while(*p && *p++ != ' '); // next
3048 looking_at(buf, &i, "*% "); // eat prompt
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056 /* skip formula vars */
3057 if (started == STARTED_NONE &&
3058 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059 started = STARTED_CHATTER;
3064 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065 if (appData.autoKibitz && started == STARTED_NONE &&
3066 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3067 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3069 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3070 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3071 suppressKibitz = TRUE;
3072 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3075 && (gameMode == IcsPlayingWhite)) ||
3076 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3077 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3078 started = STARTED_CHATTER; // own kibitz we simply discard
3080 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3081 parse_pos = 0; parse[0] = NULLCHAR;
3082 savingComment = TRUE;
3083 suppressKibitz = gameMode != IcsObserving ? 2 :
3084 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3088 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3089 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3090 && atoi(star_match[0])) {
3091 // suppress the acknowledgements of our own autoKibitz
3093 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3095 SendToPlayer(star_match[0], strlen(star_match[0]));
3096 if(looking_at(buf, &i, "*% ")) // eat prompt
3097 suppressKibitz = FALSE;
3101 } // [HGM] kibitz: end of patch
3103 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3105 // [HGM] chat: intercept tells by users for which we have an open chat window
3107 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3108 looking_at(buf, &i, "* whispers:") ||
3109 looking_at(buf, &i, "* kibitzes:") ||
3110 looking_at(buf, &i, "* shouts:") ||
3111 looking_at(buf, &i, "* c-shouts:") ||
3112 looking_at(buf, &i, "--> * ") ||
3113 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3114 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3115 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3116 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3118 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3119 chattingPartner = -1;
3121 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3122 for(p=0; p<MAX_CHAT; p++) {
3123 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3124 talker[0] = '['; strcat(talker, "] ");
3125 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3126 chattingPartner = p; break;
3129 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3130 for(p=0; p<MAX_CHAT; p++) {
3131 if(!strcmp("kibitzes", chatPartner[p])) {
3132 talker[0] = '['; strcat(talker, "] ");
3133 chattingPartner = p; break;
3136 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3137 for(p=0; p<MAX_CHAT; p++) {
3138 if(!strcmp("whispers", chatPartner[p])) {
3139 talker[0] = '['; strcat(talker, "] ");
3140 chattingPartner = p; break;
3143 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3144 if(buf[i-8] == '-' && buf[i-3] == 't')
3145 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3146 if(!strcmp("c-shouts", chatPartner[p])) {
3147 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3148 chattingPartner = p; break;
3151 if(chattingPartner < 0)
3152 for(p=0; p<MAX_CHAT; p++) {
3153 if(!strcmp("shouts", chatPartner[p])) {
3154 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3155 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3156 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3157 chattingPartner = p; break;
3161 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3162 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3163 talker[0] = 0; Colorize(ColorTell, FALSE);
3164 chattingPartner = p; break;
3166 if(chattingPartner<0) i = oldi; else {
3167 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3168 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3169 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170 started = STARTED_COMMENT;
3171 parse_pos = 0; parse[0] = NULLCHAR;
3172 savingComment = 3 + chattingPartner; // counts as TRUE
3173 suppressKibitz = TRUE;
3176 } // [HGM] chat: end of patch
3179 if (appData.zippyTalk || appData.zippyPlay) {
3180 /* [DM] Backup address for color zippy lines */
3182 if (loggedOn == TRUE)
3183 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3184 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3186 } // [DM] 'else { ' deleted
3188 /* Regular tells and says */
3189 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3190 looking_at(buf, &i, "* (your partner) tells you: ") ||
3191 looking_at(buf, &i, "* says: ") ||
3192 /* Don't color "message" or "messages" output */
3193 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3194 looking_at(buf, &i, "*. * at *:*: ") ||
3195 looking_at(buf, &i, "--* (*:*): ") ||
3196 /* Message notifications (same color as tells) */
3197 looking_at(buf, &i, "* has left a message ") ||
3198 looking_at(buf, &i, "* just sent you a message:\n") ||
3199 /* Whispers and kibitzes */
3200 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3201 looking_at(buf, &i, "* kibitzes: ") ||
3203 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3205 if (tkind == 1 && strchr(star_match[0], ':')) {
3206 /* Avoid "tells you:" spoofs in channels */
3209 if (star_match[0][0] == NULLCHAR ||
3210 strchr(star_match[0], ' ') ||
3211 (tkind == 3 && strchr(star_match[1], ' '))) {
3212 /* Reject bogus matches */
3215 if (appData.colorize) {
3216 if (oldi > next_out) {
3217 SendToPlayer(&buf[next_out], oldi - next_out);
3222 Colorize(ColorTell, FALSE);
3223 curColor = ColorTell;
3226 Colorize(ColorKibitz, FALSE);
3227 curColor = ColorKibitz;
3230 p = strrchr(star_match[1], '(');
3237 Colorize(ColorChannel1, FALSE);
3238 curColor = ColorChannel1;
3240 Colorize(ColorChannel, FALSE);
3241 curColor = ColorChannel;
3245 curColor = ColorNormal;
3249 if (started == STARTED_NONE && appData.autoComment &&
3250 (gameMode == IcsObserving ||
3251 gameMode == IcsPlayingWhite ||
3252 gameMode == IcsPlayingBlack)) {
3253 parse_pos = i - oldi;
3254 memcpy(parse, &buf[oldi], parse_pos);
3255 parse[parse_pos] = NULLCHAR;
3256 started = STARTED_COMMENT;
3257 savingComment = TRUE;
3259 started = STARTED_CHATTER;
3260 savingComment = FALSE;
3267 if (looking_at(buf, &i, "* s-shouts: ") ||
3268 looking_at(buf, &i, "* c-shouts: ")) {
3269 if (appData.colorize) {
3270 if (oldi > next_out) {
3271 SendToPlayer(&buf[next_out], oldi - next_out);
3274 Colorize(ColorSShout, FALSE);
3275 curColor = ColorSShout;
3278 started = STARTED_CHATTER;
3282 if (looking_at(buf, &i, "--->")) {
3287 if (looking_at(buf, &i, "* shouts: ") ||
3288 looking_at(buf, &i, "--> ")) {
3289 if (appData.colorize) {
3290 if (oldi > next_out) {
3291 SendToPlayer(&buf[next_out], oldi - next_out);
3294 Colorize(ColorShout, FALSE);
3295 curColor = ColorShout;
3298 started = STARTED_CHATTER;
3302 if (looking_at( buf, &i, "Challenge:")) {
3303 if (appData.colorize) {
3304 if (oldi > next_out) {
3305 SendToPlayer(&buf[next_out], oldi - next_out);
3308 Colorize(ColorChallenge, FALSE);
3309 curColor = ColorChallenge;
3315 if (looking_at(buf, &i, "* offers you") ||
3316 looking_at(buf, &i, "* offers to be") ||
3317 looking_at(buf, &i, "* would like to") ||
3318 looking_at(buf, &i, "* requests to") ||
3319 looking_at(buf, &i, "Your opponent offers") ||
3320 looking_at(buf, &i, "Your opponent requests")) {
3322 if (appData.colorize) {
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 Colorize(ColorRequest, FALSE);
3328 curColor = ColorRequest;
3333 if (looking_at(buf, &i, "* (*) seeking")) {
3334 if (appData.colorize) {
3335 if (oldi > next_out) {
3336 SendToPlayer(&buf[next_out], oldi - next_out);
3339 Colorize(ColorSeek, FALSE);
3340 curColor = ColorSeek;
3345 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3347 if (looking_at(buf, &i, "\\ ")) {
3348 if (prevColor != ColorNormal) {
3349 if (oldi > next_out) {
3350 SendToPlayer(&buf[next_out], oldi - next_out);
3353 Colorize(prevColor, TRUE);
3354 curColor = prevColor;
3356 if (savingComment) {
3357 parse_pos = i - oldi;
3358 memcpy(parse, &buf[oldi], parse_pos);
3359 parse[parse_pos] = NULLCHAR;
3360 started = STARTED_COMMENT;
3361 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3362 chattingPartner = savingComment - 3; // kludge to remember the box
3364 started = STARTED_CHATTER;
3369 if (looking_at(buf, &i, "Black Strength :") ||
3370 looking_at(buf, &i, "<<< style 10 board >>>") ||
3371 looking_at(buf, &i, "<10>") ||
3372 looking_at(buf, &i, "#@#")) {
3373 /* Wrong board style */
3375 SendToICS(ics_prefix);
3376 SendToICS("set style 12\n");
3377 SendToICS(ics_prefix);
3378 SendToICS("refresh\n");
3382 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3384 have_sent_ICS_logon = 1;
3388 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3389 (looking_at(buf, &i, "\n<12> ") ||
3390 looking_at(buf, &i, "<12> "))) {
3392 if (oldi > next_out) {
3393 SendToPlayer(&buf[next_out], oldi - next_out);
3396 started = STARTED_BOARD;
3401 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3402 looking_at(buf, &i, "<b1> ")) {
3403 if (oldi > next_out) {
3404 SendToPlayer(&buf[next_out], oldi - next_out);
3407 started = STARTED_HOLDINGS;
3412 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3414 /* Header for a move list -- first line */
3416 switch (ics_getting_history) {
3420 case BeginningOfGame:
3421 /* User typed "moves" or "oldmoves" while we
3422 were idle. Pretend we asked for these
3423 moves and soak them up so user can step
3424 through them and/or save them.
3427 gameMode = IcsObserving;
3430 ics_getting_history = H_GOT_UNREQ_HEADER;
3432 case EditGame: /*?*/
3433 case EditPosition: /*?*/
3434 /* Should above feature work in these modes too? */
3435 /* For now it doesn't */
3436 ics_getting_history = H_GOT_UNWANTED_HEADER;
3439 ics_getting_history = H_GOT_UNWANTED_HEADER;
3444 /* Is this the right one? */
3445 if (gameInfo.white && gameInfo.black &&
3446 strcmp(gameInfo.white, star_match[0]) == 0 &&
3447 strcmp(gameInfo.black, star_match[2]) == 0) {
3449 ics_getting_history = H_GOT_REQ_HEADER;
3452 case H_GOT_REQ_HEADER:
3453 case H_GOT_UNREQ_HEADER:
3454 case H_GOT_UNWANTED_HEADER:
3455 case H_GETTING_MOVES:
3456 /* Should not happen */
3457 DisplayError(_("Error gathering move list: two headers"), 0);
3458 ics_getting_history = H_FALSE;
3462 /* Save player ratings into gameInfo if needed */
3463 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3464 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3465 (gameInfo.whiteRating == -1 ||
3466 gameInfo.blackRating == -1)) {
3468 gameInfo.whiteRating = string_to_rating(star_match[1]);
3469 gameInfo.blackRating = string_to_rating(star_match[3]);
3470 if (appData.debugMode)
3471 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3472 gameInfo.whiteRating, gameInfo.blackRating);
3477 if (looking_at(buf, &i,
3478 "* * match, initial time: * minute*, increment: * second")) {
3479 /* Header for a move list -- second line */
3480 /* Initial board will follow if this is a wild game */
3481 if (gameInfo.event != NULL) free(gameInfo.event);
3482 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3483 gameInfo.event = StrSave(str);
3484 /* [HGM] we switched variant. Translate boards if needed. */
3485 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3489 if (looking_at(buf, &i, "Move ")) {
3490 /* Beginning of a move list */
3491 switch (ics_getting_history) {
3493 /* Normally should not happen */
3494 /* Maybe user hit reset while we were parsing */
3497 /* Happens if we are ignoring a move list that is not
3498 * the one we just requested. Common if the user
3499 * tries to observe two games without turning off
3502 case H_GETTING_MOVES:
3503 /* Should not happen */
3504 DisplayError(_("Error gathering move list: nested"), 0);
3505 ics_getting_history = H_FALSE;
3507 case H_GOT_REQ_HEADER:
3508 ics_getting_history = H_GETTING_MOVES;
3509 started = STARTED_MOVES;
3511 if (oldi > next_out) {
3512 SendToPlayer(&buf[next_out], oldi - next_out);
3515 case H_GOT_UNREQ_HEADER:
3516 ics_getting_history = H_GETTING_MOVES;
3517 started = STARTED_MOVES_NOHIDE;
3520 case H_GOT_UNWANTED_HEADER:
3521 ics_getting_history = H_FALSE;
3527 if (looking_at(buf, &i, "% ") ||
3528 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3529 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3530 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3531 soughtPending = FALSE;
3535 if(suppressKibitz) next_out = i;
3536 savingComment = FALSE;
3540 case STARTED_MOVES_NOHIDE:
3541 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3542 parse[parse_pos + i - oldi] = NULLCHAR;
3543 ParseGameHistory(parse);
3545 if (appData.zippyPlay && first.initDone) {
3546 FeedMovesToProgram(&first, forwardMostMove);
3547 if (gameMode == IcsPlayingWhite) {
3548 if (WhiteOnMove(forwardMostMove)) {
3549 if (first.sendTime) {
3550 if (first.useColors) {
3551 SendToProgram("black\n", &first);
3553 SendTimeRemaining(&first, TRUE);
3555 if (first.useColors) {
3556 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3558 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3559 first.maybeThinking = TRUE;
3561 if (first.usePlayother) {
3562 if (first.sendTime) {
3563 SendTimeRemaining(&first, TRUE);
3565 SendToProgram("playother\n", &first);
3571 } else if (gameMode == IcsPlayingBlack) {
3572 if (!WhiteOnMove(forwardMostMove)) {
3573 if (first.sendTime) {
3574 if (first.useColors) {
3575 SendToProgram("white\n", &first);
3577 SendTimeRemaining(&first, FALSE);
3579 if (first.useColors) {
3580 SendToProgram("black\n", &first);
3582 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3583 first.maybeThinking = TRUE;
3585 if (first.usePlayother) {
3586 if (first.sendTime) {
3587 SendTimeRemaining(&first, FALSE);
3589 SendToProgram("playother\n", &first);
3598 if (gameMode == IcsObserving && ics_gamenum == -1) {
3599 /* Moves came from oldmoves or moves command
3600 while we weren't doing anything else.
3602 currentMove = forwardMostMove;
3603 ClearHighlights();/*!!could figure this out*/
3604 flipView = appData.flipView;
3605 DrawPosition(TRUE, boards[currentMove]);
3606 DisplayBothClocks();
3607 snprintf(str, MSG_SIZ, "%s %s %s",
3608 gameInfo.white, _("vs."), gameInfo.black);
3612 /* Moves were history of an active game */
3613 if (gameInfo.resultDetails != NULL) {
3614 free(gameInfo.resultDetails);
3615 gameInfo.resultDetails = NULL;
3618 HistorySet(parseList, backwardMostMove,
3619 forwardMostMove, currentMove-1);
3620 DisplayMove(currentMove - 1);
3621 if (started == STARTED_MOVES) next_out = i;
3622 started = STARTED_NONE;
3623 ics_getting_history = H_FALSE;
3626 case STARTED_OBSERVE:
3627 started = STARTED_NONE;
3628 SendToICS(ics_prefix);
3629 SendToICS("refresh\n");
3635 if(bookHit) { // [HGM] book: simulate book reply
3636 static char bookMove[MSG_SIZ]; // a bit generous?
3638 programStats.nodes = programStats.depth = programStats.time =
3639 programStats.score = programStats.got_only_move = 0;
3640 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3642 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3643 strcat(bookMove, bookHit);
3644 HandleMachineMove(bookMove, &first);
3649 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3650 started == STARTED_HOLDINGS ||
3651 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3652 /* Accumulate characters in move list or board */
3653 parse[parse_pos++] = buf[i];
3656 /* Start of game messages. Mostly we detect start of game
3657 when the first board image arrives. On some versions
3658 of the ICS, though, we need to do a "refresh" after starting
3659 to observe in order to get the current board right away. */
3660 if (looking_at(buf, &i, "Adding game * to observation list")) {
3661 started = STARTED_OBSERVE;
3665 /* Handle auto-observe */
3666 if (appData.autoObserve &&
3667 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3668 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3670 /* Choose the player that was highlighted, if any. */
3671 if (star_match[0][0] == '\033' ||
3672 star_match[1][0] != '\033') {
3673 player = star_match[0];
3675 player = star_match[2];
3677 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3678 ics_prefix, StripHighlightAndTitle(player));
3681 /* Save ratings from notify string */
3682 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3683 player1Rating = string_to_rating(star_match[1]);
3684 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3685 player2Rating = string_to_rating(star_match[3]);
3687 if (appData.debugMode)
3689 "Ratings from 'Game notification:' %s %d, %s %d\n",
3690 player1Name, player1Rating,
3691 player2Name, player2Rating);
3696 /* Deal with automatic examine mode after a game,
3697 and with IcsObserving -> IcsExamining transition */
3698 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3699 looking_at(buf, &i, "has made you an examiner of game *")) {
3701 int gamenum = atoi(star_match[0]);
3702 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3703 gamenum == ics_gamenum) {
3704 /* We were already playing or observing this game;
3705 no need to refetch history */
3706 gameMode = IcsExamining;
3708 pauseExamForwardMostMove = forwardMostMove;
3709 } else if (currentMove < forwardMostMove) {
3710 ForwardInner(forwardMostMove);
3713 /* I don't think this case really can happen */
3714 SendToICS(ics_prefix);
3715 SendToICS("refresh\n");
3720 /* Error messages */
3721 // if (ics_user_moved) {
3722 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3723 if (looking_at(buf, &i, "Illegal move") ||
3724 looking_at(buf, &i, "Not a legal move") ||
3725 looking_at(buf, &i, "Your king is in check") ||
3726 looking_at(buf, &i, "It isn't your turn") ||
3727 looking_at(buf, &i, "It is not your move")) {
3729 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3730 currentMove = forwardMostMove-1;
3731 DisplayMove(currentMove - 1); /* before DMError */
3732 DrawPosition(FALSE, boards[currentMove]);
3733 SwitchClocks(forwardMostMove-1); // [HGM] race
3734 DisplayBothClocks();
3736 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3742 if (looking_at(buf, &i, "still have time") ||
3743 looking_at(buf, &i, "not out of time") ||
3744 looking_at(buf, &i, "either player is out of time") ||
3745 looking_at(buf, &i, "has timeseal; checking")) {
3746 /* We must have called his flag a little too soon */
3747 whiteFlag = blackFlag = FALSE;
3751 if (looking_at(buf, &i, "added * seconds to") ||
3752 looking_at(buf, &i, "seconds were added to")) {
3753 /* Update the clocks */
3754 SendToICS(ics_prefix);
3755 SendToICS("refresh\n");
3759 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3760 ics_clock_paused = TRUE;
3765 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3766 ics_clock_paused = FALSE;
3771 /* Grab player ratings from the Creating: message.
3772 Note we have to check for the special case when
3773 the ICS inserts things like [white] or [black]. */
3774 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3775 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3777 0 player 1 name (not necessarily white)
3779 2 empty, white, or black (IGNORED)
3780 3 player 2 name (not necessarily black)
3783 The names/ratings are sorted out when the game
3784 actually starts (below).
3786 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3787 player1Rating = string_to_rating(star_match[1]);
3788 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3789 player2Rating = string_to_rating(star_match[4]);
3791 if (appData.debugMode)
3793 "Ratings from 'Creating:' %s %d, %s %d\n",
3794 player1Name, player1Rating,
3795 player2Name, player2Rating);
3800 /* Improved generic start/end-of-game messages */
3801 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3802 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3803 /* If tkind == 0: */
3804 /* star_match[0] is the game number */
3805 /* [1] is the white player's name */
3806 /* [2] is the black player's name */
3807 /* For end-of-game: */
3808 /* [3] is the reason for the game end */
3809 /* [4] is a PGN end game-token, preceded by " " */
3810 /* For start-of-game: */
3811 /* [3] begins with "Creating" or "Continuing" */
3812 /* [4] is " *" or empty (don't care). */
3813 int gamenum = atoi(star_match[0]);
3814 char *whitename, *blackname, *why, *endtoken;
3815 ChessMove endtype = EndOfFile;
3818 whitename = star_match[1];
3819 blackname = star_match[2];
3820 why = star_match[3];
3821 endtoken = star_match[4];
3823 whitename = star_match[1];
3824 blackname = star_match[3];
3825 why = star_match[5];
3826 endtoken = star_match[6];
3829 /* Game start messages */
3830 if (strncmp(why, "Creating ", 9) == 0 ||
3831 strncmp(why, "Continuing ", 11) == 0) {
3832 gs_gamenum = gamenum;
3833 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3834 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3835 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3837 if (appData.zippyPlay) {
3838 ZippyGameStart(whitename, blackname);
3841 partnerBoardValid = FALSE; // [HGM] bughouse
3845 /* Game end messages */
3846 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3847 ics_gamenum != gamenum) {
3850 while (endtoken[0] == ' ') endtoken++;
3851 switch (endtoken[0]) {
3854 endtype = GameUnfinished;
3857 endtype = BlackWins;
3860 if (endtoken[1] == '/')
3861 endtype = GameIsDrawn;
3863 endtype = WhiteWins;
3866 GameEnds(endtype, why, GE_ICS);
3868 if (appData.zippyPlay && first.initDone) {
3869 ZippyGameEnd(endtype, why);
3870 if (first.pr == NoProc) {
3871 /* Start the next process early so that we'll
3872 be ready for the next challenge */
3873 StartChessProgram(&first);
3875 /* Send "new" early, in case this command takes
3876 a long time to finish, so that we'll be ready
3877 for the next challenge. */
3878 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3882 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3886 if (looking_at(buf, &i, "Removing game * from observation") ||
3887 looking_at(buf, &i, "no longer observing game *") ||
3888 looking_at(buf, &i, "Game * (*) has no examiners")) {
3889 if (gameMode == IcsObserving &&
3890 atoi(star_match[0]) == ics_gamenum)
3892 /* icsEngineAnalyze */
3893 if (appData.icsEngineAnalyze) {
3900 ics_user_moved = FALSE;
3905 if (looking_at(buf, &i, "no longer examining game *")) {
3906 if (gameMode == IcsExamining &&
3907 atoi(star_match[0]) == ics_gamenum)
3911 ics_user_moved = FALSE;
3916 /* Advance leftover_start past any newlines we find,
3917 so only partial lines can get reparsed */
3918 if (looking_at(buf, &i, "\n")) {
3919 prevColor = curColor;
3920 if (curColor != ColorNormal) {
3921 if (oldi > next_out) {
3922 SendToPlayer(&buf[next_out], oldi - next_out);
3925 Colorize(ColorNormal, FALSE);
3926 curColor = ColorNormal;
3928 if (started == STARTED_BOARD) {
3929 started = STARTED_NONE;
3930 parse[parse_pos] = NULLCHAR;
3931 ParseBoard12(parse);
3934 /* Send premove here */
3935 if (appData.premove) {
3937 if (currentMove == 0 &&
3938 gameMode == IcsPlayingWhite &&
3939 appData.premoveWhite) {
3940 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3941 if (appData.debugMode)
3942 fprintf(debugFP, "Sending premove:\n");
3944 } else if (currentMove == 1 &&
3945 gameMode == IcsPlayingBlack &&
3946 appData.premoveBlack) {
3947 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3948 if (appData.debugMode)
3949 fprintf(debugFP, "Sending premove:\n");
3951 } else if (gotPremove) {
3953 ClearPremoveHighlights();
3954 if (appData.debugMode)
3955 fprintf(debugFP, "Sending premove:\n");
3956 UserMoveEvent(premoveFromX, premoveFromY,
3957 premoveToX, premoveToY,
3962 /* Usually suppress following prompt */
3963 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3964 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3965 if (looking_at(buf, &i, "*% ")) {
3966 savingComment = FALSE;
3971 } else if (started == STARTED_HOLDINGS) {
3973 char new_piece[MSG_SIZ];
3974 started = STARTED_NONE;
3975 parse[parse_pos] = NULLCHAR;
3976 if (appData.debugMode)
3977 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3978 parse, currentMove);
3979 if (sscanf(parse, " game %d", &gamenum) == 1) {
3980 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3981 if (gameInfo.variant == VariantNormal) {
3982 /* [HGM] We seem to switch variant during a game!
3983 * Presumably no holdings were displayed, so we have
3984 * to move the position two files to the right to
3985 * create room for them!
3987 VariantClass newVariant;
3988 switch(gameInfo.boardWidth) { // base guess on board width
3989 case 9: newVariant = VariantShogi; break;
3990 case 10: newVariant = VariantGreat; break;
3991 default: newVariant = VariantCrazyhouse; break;
3993 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3994 /* Get a move list just to see the header, which
3995 will tell us whether this is really bug or zh */
3996 if (ics_getting_history == H_FALSE) {
3997 ics_getting_history = H_REQUESTED;
3998 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4002 new_piece[0] = NULLCHAR;
4003 sscanf(parse, "game %d white [%s black [%s <- %s",
4004 &gamenum, white_holding, black_holding,
4006 white_holding[strlen(white_holding)-1] = NULLCHAR;
4007 black_holding[strlen(black_holding)-1] = NULLCHAR;
4008 /* [HGM] copy holdings to board holdings area */
4009 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4010 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4011 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4013 if (appData.zippyPlay && first.initDone) {
4014 ZippyHoldings(white_holding, black_holding,
4018 if (tinyLayout || smallLayout) {
4019 char wh[16], bh[16];
4020 PackHolding(wh, white_holding);
4021 PackHolding(bh, black_holding);
4022 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4023 gameInfo.white, gameInfo.black);
4025 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4026 gameInfo.white, white_holding, _("vs."),
4027 gameInfo.black, black_holding);
4029 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4030 DrawPosition(FALSE, boards[currentMove]);
4032 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4033 sscanf(parse, "game %d white [%s black [%s <- %s",
4034 &gamenum, white_holding, black_holding,
4036 white_holding[strlen(white_holding)-1] = NULLCHAR;
4037 black_holding[strlen(black_holding)-1] = NULLCHAR;
4038 /* [HGM] copy holdings to partner-board holdings area */
4039 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4040 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4041 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4042 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4043 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4046 /* Suppress following prompt */
4047 if (looking_at(buf, &i, "*% ")) {
4048 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4049 savingComment = FALSE;
4057 i++; /* skip unparsed character and loop back */
4060 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4061 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4062 // SendToPlayer(&buf[next_out], i - next_out);
4063 started != STARTED_HOLDINGS && leftover_start > next_out) {
4064 SendToPlayer(&buf[next_out], leftover_start - next_out);
4068 leftover_len = buf_len - leftover_start;
4069 /* if buffer ends with something we couldn't parse,
4070 reparse it after appending the next read */
4072 } else if (count == 0) {
4073 RemoveInputSource(isr);
4074 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4076 DisplayFatalError(_("Error reading from ICS"), error, 1);
4081 /* Board style 12 looks like this:
4083 <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
4085 * The "<12> " is stripped before it gets to this routine. The two
4086 * trailing 0's (flip state and clock ticking) are later addition, and
4087 * some chess servers may not have them, or may have only the first.
4088 * Additional trailing fields may be added in the future.
4091 #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"
4093 #define RELATION_OBSERVING_PLAYED 0
4094 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4095 #define RELATION_PLAYING_MYMOVE 1
4096 #define RELATION_PLAYING_NOTMYMOVE -1
4097 #define RELATION_EXAMINING 2
4098 #define RELATION_ISOLATED_BOARD -3
4099 #define RELATION_STARTING_POSITION -4 /* FICS only */
4102 ParseBoard12 (char *string)
4104 GameMode newGameMode;
4105 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4106 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4107 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4108 char to_play, board_chars[200];
4109 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4110 char black[32], white[32];
4112 int prevMove = currentMove;
4115 int fromX, fromY, toX, toY;
4117 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4118 char *bookHit = NULL; // [HGM] book
4119 Boolean weird = FALSE, reqFlag = FALSE;
4121 fromX = fromY = toX = toY = -1;
4125 if (appData.debugMode)
4126 fprintf(debugFP, _("Parsing board: %s\n"), string);
4128 move_str[0] = NULLCHAR;
4129 elapsed_time[0] = NULLCHAR;
4130 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4132 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4133 if(string[i] == ' ') { ranks++; files = 0; }
4135 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4138 for(j = 0; j <i; j++) board_chars[j] = string[j];
4139 board_chars[i] = '\0';
4142 n = sscanf(string, PATTERN, &to_play, &double_push,
4143 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4144 &gamenum, white, black, &relation, &basetime, &increment,
4145 &white_stren, &black_stren, &white_time, &black_time,
4146 &moveNum, str, elapsed_time, move_str, &ics_flip,
4150 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4151 DisplayError(str, 0);
4155 /* Convert the move number to internal form */
4156 moveNum = (moveNum - 1) * 2;
4157 if (to_play == 'B') moveNum++;
4158 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4159 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4165 case RELATION_OBSERVING_PLAYED:
4166 case RELATION_OBSERVING_STATIC:
4167 if (gamenum == -1) {
4168 /* Old ICC buglet */
4169 relation = RELATION_OBSERVING_STATIC;
4171 newGameMode = IcsObserving;
4173 case RELATION_PLAYING_MYMOVE:
4174 case RELATION_PLAYING_NOTMYMOVE:
4176 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4177 IcsPlayingWhite : IcsPlayingBlack;
4179 case RELATION_EXAMINING:
4180 newGameMode = IcsExamining;
4182 case RELATION_ISOLATED_BOARD:
4184 /* Just display this board. If user was doing something else,
4185 we will forget about it until the next board comes. */
4186 newGameMode = IcsIdle;
4188 case RELATION_STARTING_POSITION:
4189 newGameMode = gameMode;
4193 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4194 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4195 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4197 for (k = 0; k < ranks; k++) {
4198 for (j = 0; j < files; j++)
4199 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4200 if(gameInfo.holdingsWidth > 1) {
4201 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4202 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4205 CopyBoard(partnerBoard, board);
4206 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4207 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4208 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4209 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4210 if(toSqr = strchr(str, '-')) {
4211 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4212 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4213 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4214 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4215 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4216 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4217 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4218 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4219 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4220 DisplayMessage(partnerStatus, "");
4221 partnerBoardValid = TRUE;
4225 /* Modify behavior for initial board display on move listing
4228 switch (ics_getting_history) {
4232 case H_GOT_REQ_HEADER:
4233 case H_GOT_UNREQ_HEADER:
4234 /* This is the initial position of the current game */
4235 gamenum = ics_gamenum;
4236 moveNum = 0; /* old ICS bug workaround */
4237 if (to_play == 'B') {
4238 startedFromSetupPosition = TRUE;
4239 blackPlaysFirst = TRUE;
4241 if (forwardMostMove == 0) forwardMostMove = 1;
4242 if (backwardMostMove == 0) backwardMostMove = 1;
4243 if (currentMove == 0) currentMove = 1;
4245 newGameMode = gameMode;
4246 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4248 case H_GOT_UNWANTED_HEADER:
4249 /* This is an initial board that we don't want */
4251 case H_GETTING_MOVES:
4252 /* Should not happen */
4253 DisplayError(_("Error gathering move list: extra board"), 0);
4254 ics_getting_history = H_FALSE;
4258 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4259 weird && (int)gameInfo.variant < (int)VariantShogi) {
4260 /* [HGM] We seem to have switched variant unexpectedly
4261 * Try to guess new variant from board size
4263 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4264 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4265 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4266 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4267 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4268 if(!weird) newVariant = VariantNormal;
4269 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4270 /* Get a move list just to see the header, which
4271 will tell us whether this is really bug or zh */
4272 if (ics_getting_history == H_FALSE) {
4273 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4274 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4279 /* Take action if this is the first board of a new game, or of a
4280 different game than is currently being displayed. */
4281 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4282 relation == RELATION_ISOLATED_BOARD) {
4284 /* Forget the old game and get the history (if any) of the new one */
4285 if (gameMode != BeginningOfGame) {
4289 if (appData.autoRaiseBoard) BoardToTop();
4291 if (gamenum == -1) {
4292 newGameMode = IcsIdle;
4293 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4294 appData.getMoveList && !reqFlag) {
4295 /* Need to get game history */
4296 ics_getting_history = H_REQUESTED;
4297 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4301 /* Initially flip the board to have black on the bottom if playing
4302 black or if the ICS flip flag is set, but let the user change
4303 it with the Flip View button. */
4304 flipView = appData.autoFlipView ?
4305 (newGameMode == IcsPlayingBlack) || ics_flip :
4308 /* Done with values from previous mode; copy in new ones */
4309 gameMode = newGameMode;
4311 ics_gamenum = gamenum;
4312 if (gamenum == gs_gamenum) {
4313 int klen = strlen(gs_kind);
4314 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4315 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4316 gameInfo.event = StrSave(str);
4318 gameInfo.event = StrSave("ICS game");
4320 gameInfo.site = StrSave(appData.icsHost);
4321 gameInfo.date = PGNDate();
4322 gameInfo.round = StrSave("-");
4323 gameInfo.white = StrSave(white);
4324 gameInfo.black = StrSave(black);
4325 timeControl = basetime * 60 * 1000;
4327 timeIncrement = increment * 1000;
4328 movesPerSession = 0;
4329 gameInfo.timeControl = TimeControlTagValue();
4330 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4331 if (appData.debugMode) {
4332 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4333 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4334 setbuf(debugFP, NULL);
4337 gameInfo.outOfBook = NULL;
4339 /* Do we have the ratings? */
4340 if (strcmp(player1Name, white) == 0 &&
4341 strcmp(player2Name, black) == 0) {
4342 if (appData.debugMode)
4343 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4344 player1Rating, player2Rating);
4345 gameInfo.whiteRating = player1Rating;
4346 gameInfo.blackRating = player2Rating;
4347 } else if (strcmp(player2Name, white) == 0 &&
4348 strcmp(player1Name, black) == 0) {
4349 if (appData.debugMode)
4350 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4351 player2Rating, player1Rating);
4352 gameInfo.whiteRating = player2Rating;
4353 gameInfo.blackRating = player1Rating;
4355 player1Name[0] = player2Name[0] = NULLCHAR;
4357 /* Silence shouts if requested */
4358 if (appData.quietPlay &&
4359 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4360 SendToICS(ics_prefix);
4361 SendToICS("set shout 0\n");
4365 /* Deal with midgame name changes */
4367 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4368 if (gameInfo.white) free(gameInfo.white);
4369 gameInfo.white = StrSave(white);
4371 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4372 if (gameInfo.black) free(gameInfo.black);
4373 gameInfo.black = StrSave(black);
4377 /* Throw away game result if anything actually changes in examine mode */
4378 if (gameMode == IcsExamining && !newGame) {
4379 gameInfo.result = GameUnfinished;
4380 if (gameInfo.resultDetails != NULL) {
4381 free(gameInfo.resultDetails);
4382 gameInfo.resultDetails = NULL;
4386 /* In pausing && IcsExamining mode, we ignore boards coming
4387 in if they are in a different variation than we are. */
4388 if (pauseExamInvalid) return;
4389 if (pausing && gameMode == IcsExamining) {
4390 if (moveNum <= pauseExamForwardMostMove) {
4391 pauseExamInvalid = TRUE;
4392 forwardMostMove = pauseExamForwardMostMove;
4397 if (appData.debugMode) {
4398 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4400 /* Parse the board */
4401 for (k = 0; k < ranks; k++) {
4402 for (j = 0; j < files; j++)
4403 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4404 if(gameInfo.holdingsWidth > 1) {
4405 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4406 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4410 board[5][BOARD_RGHT+1] = WhiteAngel;
4411 board[6][BOARD_RGHT+1] = WhiteMarshall;
4412 board[1][0] = BlackMarshall;
4413 board[2][0] = BlackAngel;
4414 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4416 CopyBoard(boards[moveNum], board);
4417 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4419 startedFromSetupPosition =
4420 !CompareBoards(board, initialPosition);
4421 if(startedFromSetupPosition)
4422 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4425 /* [HGM] Set castling rights. Take the outermost Rooks,
4426 to make it also work for FRC opening positions. Note that board12
4427 is really defective for later FRC positions, as it has no way to
4428 indicate which Rook can castle if they are on the same side of King.
4429 For the initial position we grant rights to the outermost Rooks,
4430 and remember thos rights, and we then copy them on positions
4431 later in an FRC game. This means WB might not recognize castlings with
4432 Rooks that have moved back to their original position as illegal,
4433 but in ICS mode that is not its job anyway.
4435 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4436 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4438 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4439 if(board[0][i] == WhiteRook) j = i;
4440 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4441 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4442 if(board[0][i] == WhiteRook) j = i;
4443 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4445 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4446 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4448 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4452 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4453 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4454 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4455 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456 if(board[BOARD_HEIGHT-1][k] == bKing)
4457 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4458 if(gameInfo.variant == VariantTwoKings) {
4459 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4460 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4461 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4464 r = boards[moveNum][CASTLING][0] = initialRights[0];
4465 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4466 r = boards[moveNum][CASTLING][1] = initialRights[1];
4467 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4468 r = boards[moveNum][CASTLING][3] = initialRights[3];
4469 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4470 r = boards[moveNum][CASTLING][4] = initialRights[4];
4471 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4472 /* wildcastle kludge: always assume King has rights */
4473 r = boards[moveNum][CASTLING][2] = initialRights[2];
4474 r = boards[moveNum][CASTLING][5] = initialRights[5];
4476 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4477 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4480 if (ics_getting_history == H_GOT_REQ_HEADER ||
4481 ics_getting_history == H_GOT_UNREQ_HEADER) {
4482 /* This was an initial position from a move list, not
4483 the current position */
4487 /* Update currentMove and known move number limits */
4488 newMove = newGame || moveNum > forwardMostMove;
4491 forwardMostMove = backwardMostMove = currentMove = moveNum;
4492 if (gameMode == IcsExamining && moveNum == 0) {
4493 /* Workaround for ICS limitation: we are not told the wild
4494 type when starting to examine a game. But if we ask for
4495 the move list, the move list header will tell us */
4496 ics_getting_history = H_REQUESTED;
4497 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4500 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4501 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4503 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4504 /* [HGM] applied this also to an engine that is silently watching */
4505 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4506 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4507 gameInfo.variant == currentlyInitializedVariant) {
4508 takeback = forwardMostMove - moveNum;
4509 for (i = 0; i < takeback; i++) {
4510 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4511 SendToProgram("undo\n", &first);
4516 forwardMostMove = moveNum;
4517 if (!pausing || currentMove > forwardMostMove)
4518 currentMove = forwardMostMove;
4520 /* New part of history that is not contiguous with old part */
4521 if (pausing && gameMode == IcsExamining) {
4522 pauseExamInvalid = TRUE;
4523 forwardMostMove = pauseExamForwardMostMove;
4526 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4528 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4529 // [HGM] when we will receive the move list we now request, it will be
4530 // fed to the engine from the first move on. So if the engine is not
4531 // in the initial position now, bring it there.
4532 InitChessProgram(&first, 0);
4535 ics_getting_history = H_REQUESTED;
4536 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539 forwardMostMove = backwardMostMove = currentMove = moveNum;
4542 /* Update the clocks */
4543 if (strchr(elapsed_time, '.')) {
4545 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4546 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4548 /* Time is in seconds */
4549 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4550 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4555 if (appData.zippyPlay && newGame &&
4556 gameMode != IcsObserving && gameMode != IcsIdle &&
4557 gameMode != IcsExamining)
4558 ZippyFirstBoard(moveNum, basetime, increment);
4561 /* Put the move on the move list, first converting
4562 to canonical algebraic form. */
4564 if (appData.debugMode) {
4565 if (appData.debugMode) { int f = forwardMostMove;
4566 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4567 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4568 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4570 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4571 fprintf(debugFP, "moveNum = %d\n", moveNum);
4572 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4573 setbuf(debugFP, NULL);
4575 if (moveNum <= backwardMostMove) {
4576 /* We don't know what the board looked like before
4578 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4579 strcat(parseList[moveNum - 1], " ");
4580 strcat(parseList[moveNum - 1], elapsed_time);
4581 moveList[moveNum - 1][0] = NULLCHAR;
4582 } else if (strcmp(move_str, "none") == 0) {
4583 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4584 /* Again, we don't know what the board looked like;
4585 this is really the start of the game. */
4586 parseList[moveNum - 1][0] = NULLCHAR;
4587 moveList[moveNum - 1][0] = NULLCHAR;
4588 backwardMostMove = moveNum;
4589 startedFromSetupPosition = TRUE;
4590 fromX = fromY = toX = toY = -1;
4592 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4593 // So we parse the long-algebraic move string in stead of the SAN move
4594 int valid; char buf[MSG_SIZ], *prom;
4596 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4597 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4598 // str looks something like "Q/a1-a2"; kill the slash
4600 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4601 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4602 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4603 strcat(buf, prom); // long move lacks promo specification!
4604 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4605 if(appData.debugMode)
4606 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4607 safeStrCpy(move_str, buf, MSG_SIZ);
4609 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4610 &fromX, &fromY, &toX, &toY, &promoChar)
4611 || ParseOneMove(buf, moveNum - 1, &moveType,
4612 &fromX, &fromY, &toX, &toY, &promoChar);
4613 // end of long SAN patch
4615 (void) CoordsToAlgebraic(boards[moveNum - 1],
4616 PosFlags(moveNum - 1),
4617 fromY, fromX, toY, toX, promoChar,
4618 parseList[moveNum-1]);
4619 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4625 if(gameInfo.variant != VariantShogi)
4626 strcat(parseList[moveNum - 1], "+");
4629 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4630 strcat(parseList[moveNum - 1], "#");
4633 strcat(parseList[moveNum - 1], " ");
4634 strcat(parseList[moveNum - 1], elapsed_time);
4635 /* currentMoveString is set as a side-effect of ParseOneMove */
4636 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4637 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4638 strcat(moveList[moveNum - 1], "\n");
4640 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4641 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4642 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4643 ChessSquare old, new = boards[moveNum][k][j];
4644 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4645 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4646 if(old == new) continue;
4647 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4648 else if(new == WhiteWazir || new == BlackWazir) {
4649 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4650 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4651 else boards[moveNum][k][j] = old; // preserve type of Gold
4652 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4653 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4656 /* Move from ICS was illegal!? Punt. */
4657 if (appData.debugMode) {
4658 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4659 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4661 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4662 strcat(parseList[moveNum - 1], " ");
4663 strcat(parseList[moveNum - 1], elapsed_time);
4664 moveList[moveNum - 1][0] = NULLCHAR;
4665 fromX = fromY = toX = toY = -1;
4668 if (appData.debugMode) {
4669 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4670 setbuf(debugFP, NULL);
4674 /* Send move to chess program (BEFORE animating it). */
4675 if (appData.zippyPlay && !newGame && newMove &&
4676 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4678 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4679 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4680 if (moveList[moveNum - 1][0] == NULLCHAR) {
4681 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4683 DisplayError(str, 0);
4685 if (first.sendTime) {
4686 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4688 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4689 if (firstMove && !bookHit) {
4691 if (first.useColors) {
4692 SendToProgram(gameMode == IcsPlayingWhite ?
4694 "black\ngo\n", &first);
4696 SendToProgram("go\n", &first);
4698 first.maybeThinking = TRUE;
4701 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4702 if (moveList[moveNum - 1][0] == NULLCHAR) {
4703 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4704 DisplayError(str, 0);
4706 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4707 SendMoveToProgram(moveNum - 1, &first);
4714 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4715 /* If move comes from a remote source, animate it. If it
4716 isn't remote, it will have already been animated. */
4717 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4718 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4720 if (!pausing && appData.highlightLastMove) {
4721 SetHighlights(fromX, fromY, toX, toY);
4725 /* Start the clocks */
4726 whiteFlag = blackFlag = FALSE;
4727 appData.clockMode = !(basetime == 0 && increment == 0);
4729 ics_clock_paused = TRUE;
4731 } else if (ticking == 1) {
4732 ics_clock_paused = FALSE;
4734 if (gameMode == IcsIdle ||
4735 relation == RELATION_OBSERVING_STATIC ||
4736 relation == RELATION_EXAMINING ||
4738 DisplayBothClocks();
4742 /* Display opponents and material strengths */
4743 if (gameInfo.variant != VariantBughouse &&
4744 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4745 if (tinyLayout || smallLayout) {
4746 if(gameInfo.variant == VariantNormal)
4747 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4748 gameInfo.white, white_stren, gameInfo.black, black_stren,
4749 basetime, increment);
4751 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4752 gameInfo.white, white_stren, gameInfo.black, black_stren,
4753 basetime, increment, (int) gameInfo.variant);
4755 if(gameInfo.variant == VariantNormal)
4756 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4757 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4758 basetime, increment);
4760 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4761 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4762 basetime, increment, VariantName(gameInfo.variant));
4765 if (appData.debugMode) {
4766 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4771 /* Display the board */
4772 if (!pausing && !appData.noGUI) {
4774 if (appData.premove)
4776 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4777 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4778 ClearPremoveHighlights();
4780 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4781 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4782 DrawPosition(j, boards[currentMove]);
4784 DisplayMove(moveNum - 1);
4785 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4786 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4787 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4788 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4792 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4794 if(bookHit) { // [HGM] book: simulate book reply
4795 static char bookMove[MSG_SIZ]; // a bit generous?
4797 programStats.nodes = programStats.depth = programStats.time =
4798 programStats.score = programStats.got_only_move = 0;
4799 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4801 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4802 strcat(bookMove, bookHit);
4803 HandleMachineMove(bookMove, &first);
4812 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4813 ics_getting_history = H_REQUESTED;
4814 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4820 AnalysisPeriodicEvent (int force)
4822 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4823 && !force) || !appData.periodicUpdates)
4826 /* Send . command to Crafty to collect stats */
4827 SendToProgram(".\n", &first);
4829 /* Don't send another until we get a response (this makes
4830 us stop sending to old Crafty's which don't understand
4831 the "." command (sending illegal cmds resets node count & time,
4832 which looks bad)) */
4833 programStats.ok_to_send = 0;
4837 ics_update_width (int new_width)
4839 ics_printf("set width %d\n", new_width);
4843 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4847 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4848 // null move in variant where engine does not understand it (for analysis purposes)
4849 SendBoard(cps, moveNum + 1); // send position after move in stead.
4852 if (cps->useUsermove) {
4853 SendToProgram("usermove ", cps);
4857 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4858 int len = space - parseList[moveNum];
4859 memcpy(buf, parseList[moveNum], len);
4861 buf[len] = NULLCHAR;
4863 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4865 SendToProgram(buf, cps);
4867 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4868 AlphaRank(moveList[moveNum], 4);
4869 SendToProgram(moveList[moveNum], cps);
4870 AlphaRank(moveList[moveNum], 4); // and back
4872 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4873 * the engine. It would be nice to have a better way to identify castle
4875 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4876 && cps->useOOCastle) {
4877 int fromX = moveList[moveNum][0] - AAA;
4878 int fromY = moveList[moveNum][1] - ONE;
4879 int toX = moveList[moveNum][2] - AAA;
4880 int toY = moveList[moveNum][3] - ONE;
4881 if((boards[moveNum][fromY][fromX] == WhiteKing
4882 && boards[moveNum][toY][toX] == WhiteRook)
4883 || (boards[moveNum][fromY][fromX] == BlackKing
4884 && boards[moveNum][toY][toX] == BlackRook)) {
4885 if(toX > fromX) SendToProgram("O-O\n", cps);
4886 else SendToProgram("O-O-O\n", cps);
4888 else SendToProgram(moveList[moveNum], cps);
4890 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4891 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4892 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4893 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4894 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4896 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4897 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4898 SendToProgram(buf, cps);
4900 else SendToProgram(moveList[moveNum], cps);
4901 /* End of additions by Tord */
4904 /* [HGM] setting up the opening has brought engine in force mode! */
4905 /* Send 'go' if we are in a mode where machine should play. */
4906 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4907 (gameMode == TwoMachinesPlay ||
4909 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4911 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4912 SendToProgram("go\n", cps);
4913 if (appData.debugMode) {
4914 fprintf(debugFP, "(extra)\n");
4917 setboardSpoiledMachineBlack = 0;
4921 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4923 char user_move[MSG_SIZ];
4926 if(gameInfo.variant == VariantSChess && promoChar) {
4927 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4928 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4929 } else suffix[0] = NULLCHAR;
4933 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4934 (int)moveType, fromX, fromY, toX, toY);
4935 DisplayError(user_move + strlen("say "), 0);
4937 case WhiteKingSideCastle:
4938 case BlackKingSideCastle:
4939 case WhiteQueenSideCastleWild:
4940 case BlackQueenSideCastleWild:
4942 case WhiteHSideCastleFR:
4943 case BlackHSideCastleFR:
4945 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4947 case WhiteQueenSideCastle:
4948 case BlackQueenSideCastle:
4949 case WhiteKingSideCastleWild:
4950 case BlackKingSideCastleWild:
4952 case WhiteASideCastleFR:
4953 case BlackASideCastleFR:
4955 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4957 case WhiteNonPromotion:
4958 case BlackNonPromotion:
4959 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4961 case WhitePromotion:
4962 case BlackPromotion:
4963 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4964 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4965 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4966 PieceToChar(WhiteFerz));
4967 else if(gameInfo.variant == VariantGreat)
4968 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4969 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4970 PieceToChar(WhiteMan));
4972 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4973 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4979 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4980 ToUpper(PieceToChar((ChessSquare) fromX)),
4981 AAA + toX, ONE + toY);
4983 case IllegalMove: /* could be a variant we don't quite understand */
4984 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4986 case WhiteCapturesEnPassant:
4987 case BlackCapturesEnPassant:
4988 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4989 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4992 SendToICS(user_move);
4993 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4994 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4999 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5000 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5001 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5002 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5003 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5006 if(gameMode != IcsExamining) { // is this ever not the case?
5007 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5009 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5010 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5011 } else { // on FICS we must first go to general examine mode
5012 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5014 if(gameInfo.variant != VariantNormal) {
5015 // try figure out wild number, as xboard names are not always valid on ICS
5016 for(i=1; i<=36; i++) {
5017 snprintf(buf, MSG_SIZ, "wild/%d", i);
5018 if(StringToVariant(buf) == gameInfo.variant) break;
5020 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5021 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5022 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5023 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5024 SendToICS(ics_prefix);
5026 if(startedFromSetupPosition || backwardMostMove != 0) {
5027 fen = PositionToFEN(backwardMostMove, NULL);
5028 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5029 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5031 } else { // FICS: everything has to set by separate bsetup commands
5032 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5033 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5035 if(!WhiteOnMove(backwardMostMove)) {
5036 SendToICS("bsetup tomove black\n");
5038 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5039 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5041 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5042 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5044 i = boards[backwardMostMove][EP_STATUS];
5045 if(i >= 0) { // set e.p.
5046 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5052 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5053 SendToICS("bsetup done\n"); // switch to normal examining.
5055 for(i = backwardMostMove; i<last; i++) {
5057 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5060 SendToICS(ics_prefix);
5061 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5065 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5067 if (rf == DROP_RANK) {
5068 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5069 sprintf(move, "%c@%c%c\n",
5070 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5072 if (promoChar == 'x' || promoChar == NULLCHAR) {
5073 sprintf(move, "%c%c%c%c\n",
5074 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5076 sprintf(move, "%c%c%c%c%c\n",
5077 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5083 ProcessICSInitScript (FILE *f)
5087 while (fgets(buf, MSG_SIZ, f)) {
5088 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5095 static int lastX, lastY, selectFlag, dragging;
5100 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5101 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5102 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5103 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5104 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5105 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5108 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5109 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5110 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5111 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5112 if(!step) step = -1;
5113 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5114 appData.testLegality && (promoSweep == king ||
5115 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5116 ChangeDragPiece(promoSweep);
5120 PromoScroll (int x, int y)
5124 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5125 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5126 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5127 if(!step) return FALSE;
5128 lastX = x; lastY = y;
5129 if((promoSweep < BlackPawn) == flipView) step = -step;
5130 if(step > 0) selectFlag = 1;
5131 if(!selectFlag) Sweep(step);
5136 NextPiece (int step)
5138 ChessSquare piece = boards[currentMove][toY][toX];
5141 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5142 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5143 if(!step) step = -1;
5144 } while(PieceToChar(pieceSweep) == '.');
5145 boards[currentMove][toY][toX] = pieceSweep;
5146 DrawPosition(FALSE, boards[currentMove]);
5147 boards[currentMove][toY][toX] = piece;
5149 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5151 AlphaRank (char *move, int n)
5153 // char *p = move, c; int x, y;
5155 if (appData.debugMode) {
5156 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5160 move[2]>='0' && move[2]<='9' &&
5161 move[3]>='a' && move[3]<='x' ) {
5163 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5164 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5166 if(move[0]>='0' && move[0]<='9' &&
5167 move[1]>='a' && move[1]<='x' &&
5168 move[2]>='0' && move[2]<='9' &&
5169 move[3]>='a' && move[3]<='x' ) {
5170 /* input move, Shogi -> normal */
5171 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5172 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5173 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5174 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5177 move[3]>='0' && move[3]<='9' &&
5178 move[2]>='a' && move[2]<='x' ) {
5180 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5181 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5184 move[0]>='a' && move[0]<='x' &&
5185 move[3]>='0' && move[3]<='9' &&
5186 move[2]>='a' && move[2]<='x' ) {
5187 /* output move, normal -> Shogi */
5188 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5189 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5190 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5191 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5192 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5194 if (appData.debugMode) {
5195 fprintf(debugFP, " out = '%s'\n", move);
5199 char yy_textstr[8000];
5201 /* Parser for moves from gnuchess, ICS, or user typein box */
5203 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5205 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5207 switch (*moveType) {
5208 case WhitePromotion:
5209 case BlackPromotion:
5210 case WhiteNonPromotion:
5211 case BlackNonPromotion:
5213 case WhiteCapturesEnPassant:
5214 case BlackCapturesEnPassant:
5215 case WhiteKingSideCastle:
5216 case WhiteQueenSideCastle:
5217 case BlackKingSideCastle:
5218 case BlackQueenSideCastle:
5219 case WhiteKingSideCastleWild:
5220 case WhiteQueenSideCastleWild:
5221 case BlackKingSideCastleWild:
5222 case BlackQueenSideCastleWild:
5223 /* Code added by Tord: */
5224 case WhiteHSideCastleFR:
5225 case WhiteASideCastleFR:
5226 case BlackHSideCastleFR:
5227 case BlackASideCastleFR:
5228 /* End of code added by Tord */
5229 case IllegalMove: /* bug or odd chess variant */
5230 *fromX = currentMoveString[0] - AAA;
5231 *fromY = currentMoveString[1] - ONE;
5232 *toX = currentMoveString[2] - AAA;
5233 *toY = currentMoveString[3] - ONE;
5234 *promoChar = currentMoveString[4];
5235 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5236 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5237 if (appData.debugMode) {
5238 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5240 *fromX = *fromY = *toX = *toY = 0;
5243 if (appData.testLegality) {
5244 return (*moveType != IllegalMove);
5246 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5247 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5252 *fromX = *moveType == WhiteDrop ?
5253 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5254 (int) CharToPiece(ToLower(currentMoveString[0]));
5256 *toX = currentMoveString[2] - AAA;
5257 *toY = currentMoveString[3] - ONE;
5258 *promoChar = NULLCHAR;
5262 case ImpossibleMove:
5272 if (appData.debugMode) {
5273 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5276 *fromX = *fromY = *toX = *toY = 0;
5277 *promoChar = NULLCHAR;
5282 Boolean pushed = FALSE;
5283 char *lastParseAttempt;
5286 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5287 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5288 int fromX, fromY, toX, toY; char promoChar;
5293 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5294 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5297 endPV = forwardMostMove;
5299 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5300 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5301 lastParseAttempt = pv;
5302 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5303 if(appData.debugMode){
5304 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);
5306 if(!valid && nr == 0 &&
5307 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5308 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5309 // Hande case where played move is different from leading PV move
5310 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5311 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5312 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5313 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5314 endPV += 2; // if position different, keep this
5315 moveList[endPV-1][0] = fromX + AAA;
5316 moveList[endPV-1][1] = fromY + ONE;
5317 moveList[endPV-1][2] = toX + AAA;
5318 moveList[endPV-1][3] = toY + ONE;
5319 parseList[endPV-1][0] = NULLCHAR;
5320 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5323 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5324 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5325 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5326 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5327 valid++; // allow comments in PV
5331 if(endPV+1 > framePtr) break; // no space, truncate
5334 CopyBoard(boards[endPV], boards[endPV-1]);
5335 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5336 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5337 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5338 CoordsToAlgebraic(boards[endPV - 1],
5339 PosFlags(endPV - 1),
5340 fromY, fromX, toY, toX, promoChar,
5341 parseList[endPV - 1]);
5343 if(atEnd == 2) return; // used hidden, for PV conversion
5344 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5345 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5346 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5347 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5348 DrawPosition(TRUE, boards[currentMove]);
5352 MultiPV (ChessProgramState *cps)
5353 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5355 for(i=0; i<cps->nrOptions; i++)
5356 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5362 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5364 int startPV, multi, lineStart, origIndex = index;
5365 char *p, buf2[MSG_SIZ];
5367 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5368 lastX = x; lastY = y;
5369 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5370 lineStart = startPV = index;
5371 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5372 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5374 do{ while(buf[index] && buf[index] != '\n') index++;
5375 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5377 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5378 int n = first.option[multi].value;
5379 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5380 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5381 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5382 first.option[multi].value = n;
5386 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5387 *start = startPV; *end = index-1;
5394 static char buf[10*MSG_SIZ];
5395 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5397 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5398 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5399 for(i = forwardMostMove; i<endPV; i++){
5400 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5401 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5404 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5405 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5411 LoadPV (int x, int y)
5412 { // called on right mouse click to load PV
5413 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5414 lastX = x; lastY = y;
5415 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5422 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5423 if(endPV < 0) return;
5425 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5426 Boolean saveAnimate = appData.animate;
5428 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5429 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5430 } else storedGames--; // abandon shelved tail of original game
5433 forwardMostMove = currentMove;
5434 currentMove = oldFMM;
5435 appData.animate = FALSE;
5436 ToNrEvent(forwardMostMove);
5437 appData.animate = saveAnimate;
5439 currentMove = forwardMostMove;
5440 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5441 ClearPremoveHighlights();
5442 DrawPosition(TRUE, boards[currentMove]);
5446 MovePV (int x, int y, int h)
5447 { // step through PV based on mouse coordinates (called on mouse move)
5448 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5450 // we must somehow check if right button is still down (might be released off board!)
5451 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5452 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5453 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5455 lastX = x; lastY = y;
5457 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5458 if(endPV < 0) return;
5459 if(y < margin) step = 1; else
5460 if(y > h - margin) step = -1;
5461 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5462 currentMove += step;
5463 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5464 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5465 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5466 DrawPosition(FALSE, boards[currentMove]);
5470 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5471 // All positions will have equal probability, but the current method will not provide a unique
5472 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5478 int piecesLeft[(int)BlackPawn];
5479 int seed, nrOfShuffles;
5482 GetPositionNumber ()
5483 { // sets global variable seed
5486 seed = appData.defaultFrcPosition;
5487 if(seed < 0) { // randomize based on time for negative FRC position numbers
5488 for(i=0; i<50; i++) seed += random();
5489 seed = random() ^ random() >> 8 ^ random() << 8;
5490 if(seed<0) seed = -seed;
5495 put (Board board, int pieceType, int rank, int n, int shade)
5496 // put the piece on the (n-1)-th empty squares of the given shade
5500 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5501 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5502 board[rank][i] = (ChessSquare) pieceType;
5503 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5505 piecesLeft[pieceType]--;
5514 AddOnePiece (Board board, int pieceType, int rank, int shade)
5515 // calculate where the next piece goes, (any empty square), and put it there
5519 i = seed % squaresLeft[shade];
5520 nrOfShuffles *= squaresLeft[shade];
5521 seed /= squaresLeft[shade];
5522 put(board, pieceType, rank, i, shade);
5526 AddTwoPieces (Board board, int pieceType, int rank)
5527 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5529 int i, n=squaresLeft[ANY], j=n-1, k;
5531 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5532 i = seed % k; // pick one
5535 while(i >= j) i -= j--;
5536 j = n - 1 - j; i += j;
5537 put(board, pieceType, rank, j, ANY);
5538 put(board, pieceType, rank, i, ANY);
5542 SetUpShuffle (Board board, int number)
5546 GetPositionNumber(); nrOfShuffles = 1;
5548 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5549 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5550 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5552 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5554 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5555 p = (int) board[0][i];
5556 if(p < (int) BlackPawn) piecesLeft[p] ++;
5557 board[0][i] = EmptySquare;
5560 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5561 // shuffles restricted to allow normal castling put KRR first
5562 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5563 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5564 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5565 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5566 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5567 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5568 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5569 put(board, WhiteRook, 0, 0, ANY);
5570 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5573 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5574 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5575 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5576 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5577 while(piecesLeft[p] >= 2) {
5578 AddOnePiece(board, p, 0, LITE);
5579 AddOnePiece(board, p, 0, DARK);
5581 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5584 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5585 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5586 // but we leave King and Rooks for last, to possibly obey FRC restriction
5587 if(p == (int)WhiteRook) continue;
5588 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5589 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5592 // now everything is placed, except perhaps King (Unicorn) and Rooks
5594 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5595 // Last King gets castling rights
5596 while(piecesLeft[(int)WhiteUnicorn]) {
5597 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5598 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5601 while(piecesLeft[(int)WhiteKing]) {
5602 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5603 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5608 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5609 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5612 // Only Rooks can be left; simply place them all
5613 while(piecesLeft[(int)WhiteRook]) {
5614 i = put(board, WhiteRook, 0, 0, ANY);
5615 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5618 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5620 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5624 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5627 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5631 SetCharTable (char *table, const char * map)
5632 /* [HGM] moved here from winboard.c because of its general usefulness */
5633 /* Basically a safe strcpy that uses the last character as King */
5635 int result = FALSE; int NrPieces;
5637 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5638 && NrPieces >= 12 && !(NrPieces&1)) {
5639 int i; /* [HGM] Accept even length from 12 to 34 */
5641 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5642 for( i=0; i<NrPieces/2-1; i++ ) {
5644 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5646 table[(int) WhiteKing] = map[NrPieces/2-1];
5647 table[(int) BlackKing] = map[NrPieces-1];
5656 Prelude (Board board)
5657 { // [HGM] superchess: random selection of exo-pieces
5658 int i, j, k; ChessSquare p;
5659 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5661 GetPositionNumber(); // use FRC position number
5663 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5664 SetCharTable(pieceToChar, appData.pieceToCharTable);
5665 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5666 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5669 j = seed%4; seed /= 4;
5670 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5671 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5672 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5673 j = seed%3 + (seed%3 >= j); seed /= 3;
5674 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5675 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5676 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5677 j = seed%3; seed /= 3;
5678 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5679 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5680 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5681 j = seed%2 + (seed%2 >= j); seed /= 2;
5682 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5683 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5684 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5685 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5686 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5687 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5688 put(board, exoPieces[0], 0, 0, ANY);
5689 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5693 InitPosition (int redraw)
5695 ChessSquare (* pieces)[BOARD_FILES];
5696 int i, j, pawnRow, overrule,
5697 oldx = gameInfo.boardWidth,
5698 oldy = gameInfo.boardHeight,
5699 oldh = gameInfo.holdingsWidth;
5702 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5704 /* [AS] Initialize pv info list [HGM] and game status */
5706 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5707 pvInfoList[i].depth = 0;
5708 boards[i][EP_STATUS] = EP_NONE;
5709 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5712 initialRulePlies = 0; /* 50-move counter start */
5714 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5715 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5719 /* [HGM] logic here is completely changed. In stead of full positions */
5720 /* the initialized data only consist of the two backranks. The switch */
5721 /* selects which one we will use, which is than copied to the Board */
5722 /* initialPosition, which for the rest is initialized by Pawns and */
5723 /* empty squares. This initial position is then copied to boards[0], */
5724 /* possibly after shuffling, so that it remains available. */
5726 gameInfo.holdingsWidth = 0; /* default board sizes */
5727 gameInfo.boardWidth = 8;
5728 gameInfo.boardHeight = 8;
5729 gameInfo.holdingsSize = 0;
5730 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5731 for(i=0; i<BOARD_FILES-2; i++)
5732 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5733 initialPosition[EP_STATUS] = EP_NONE;
5734 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5735 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5736 SetCharTable(pieceNickName, appData.pieceNickNames);
5737 else SetCharTable(pieceNickName, "............");
5740 switch (gameInfo.variant) {
5741 case VariantFischeRandom:
5742 shuffleOpenings = TRUE;
5745 case VariantShatranj:
5746 pieces = ShatranjArray;
5747 nrCastlingRights = 0;
5748 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5751 pieces = makrukArray;
5752 nrCastlingRights = 0;
5753 startedFromSetupPosition = TRUE;
5754 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5756 case VariantTwoKings:
5757 pieces = twoKingsArray;
5760 pieces = GrandArray;
5761 nrCastlingRights = 0;
5762 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5763 gameInfo.boardWidth = 10;
5764 gameInfo.boardHeight = 10;
5765 gameInfo.holdingsSize = 7;
5767 case VariantCapaRandom:
5768 shuffleOpenings = TRUE;
5769 case VariantCapablanca:
5770 pieces = CapablancaArray;
5771 gameInfo.boardWidth = 10;
5772 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5775 pieces = GothicArray;
5776 gameInfo.boardWidth = 10;
5777 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5781 gameInfo.holdingsSize = 7;
5784 pieces = JanusArray;
5785 gameInfo.boardWidth = 10;
5786 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5787 nrCastlingRights = 6;
5788 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5789 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5790 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5791 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5792 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5793 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5796 pieces = FalconArray;
5797 gameInfo.boardWidth = 10;
5798 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5800 case VariantXiangqi:
5801 pieces = XiangqiArray;
5802 gameInfo.boardWidth = 9;
5803 gameInfo.boardHeight = 10;
5804 nrCastlingRights = 0;
5805 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5808 pieces = ShogiArray;
5809 gameInfo.boardWidth = 9;
5810 gameInfo.boardHeight = 9;
5811 gameInfo.holdingsSize = 7;
5812 nrCastlingRights = 0;
5813 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5815 case VariantCourier:
5816 pieces = CourierArray;
5817 gameInfo.boardWidth = 12;
5818 nrCastlingRights = 0;
5819 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5821 case VariantKnightmate:
5822 pieces = KnightmateArray;
5823 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5825 case VariantSpartan:
5826 pieces = SpartanArray;
5827 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5830 pieces = fairyArray;
5831 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5834 pieces = GreatArray;
5835 gameInfo.boardWidth = 10;
5836 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5837 gameInfo.holdingsSize = 8;
5841 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5842 gameInfo.holdingsSize = 8;
5843 startedFromSetupPosition = TRUE;
5845 case VariantCrazyhouse:
5846 case VariantBughouse:
5848 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5849 gameInfo.holdingsSize = 5;
5851 case VariantWildCastle:
5853 /* !!?shuffle with kings guaranteed to be on d or e file */
5854 shuffleOpenings = 1;
5856 case VariantNoCastle:
5858 nrCastlingRights = 0;
5859 /* !!?unconstrained back-rank shuffle */
5860 shuffleOpenings = 1;
5865 if(appData.NrFiles >= 0) {
5866 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5867 gameInfo.boardWidth = appData.NrFiles;
5869 if(appData.NrRanks >= 0) {
5870 gameInfo.boardHeight = appData.NrRanks;
5872 if(appData.holdingsSize >= 0) {
5873 i = appData.holdingsSize;
5874 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5875 gameInfo.holdingsSize = i;
5877 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5878 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5879 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5881 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5882 if(pawnRow < 1) pawnRow = 1;
5883 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5885 /* User pieceToChar list overrules defaults */
5886 if(appData.pieceToCharTable != NULL)
5887 SetCharTable(pieceToChar, appData.pieceToCharTable);
5889 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5891 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5892 s = (ChessSquare) 0; /* account holding counts in guard band */
5893 for( i=0; i<BOARD_HEIGHT; i++ )
5894 initialPosition[i][j] = s;
5896 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5897 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5898 initialPosition[pawnRow][j] = WhitePawn;
5899 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5900 if(gameInfo.variant == VariantXiangqi) {
5902 initialPosition[pawnRow][j] =
5903 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5904 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5905 initialPosition[2][j] = WhiteCannon;
5906 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5910 if(gameInfo.variant == VariantGrand) {
5911 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5912 initialPosition[0][j] = WhiteRook;
5913 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5916 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5918 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5921 initialPosition[1][j] = WhiteBishop;
5922 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5924 initialPosition[1][j] = WhiteRook;
5925 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5928 if( nrCastlingRights == -1) {
5929 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5930 /* This sets default castling rights from none to normal corners */
5931 /* Variants with other castling rights must set them themselves above */
5932 nrCastlingRights = 6;
5934 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5935 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5936 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5937 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5938 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5939 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5942 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5943 if(gameInfo.variant == VariantGreat) { // promotion commoners
5944 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5945 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5946 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5947 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5949 if( gameInfo.variant == VariantSChess ) {
5950 initialPosition[1][0] = BlackMarshall;
5951 initialPosition[2][0] = BlackAngel;
5952 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5953 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5954 initialPosition[1][1] = initialPosition[2][1] =
5955 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5957 if (appData.debugMode) {
5958 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5960 if(shuffleOpenings) {
5961 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5962 startedFromSetupPosition = TRUE;
5964 if(startedFromPositionFile) {
5965 /* [HGM] loadPos: use PositionFile for every new game */
5966 CopyBoard(initialPosition, filePosition);
5967 for(i=0; i<nrCastlingRights; i++)
5968 initialRights[i] = filePosition[CASTLING][i];
5969 startedFromSetupPosition = TRUE;
5972 CopyBoard(boards[0], initialPosition);
5974 if(oldx != gameInfo.boardWidth ||
5975 oldy != gameInfo.boardHeight ||
5976 oldv != gameInfo.variant ||
5977 oldh != gameInfo.holdingsWidth
5979 InitDrawingSizes(-2 ,0);
5981 oldv = gameInfo.variant;
5983 DrawPosition(TRUE, boards[currentMove]);
5987 SendBoard (ChessProgramState *cps, int moveNum)
5989 char message[MSG_SIZ];
5991 if (cps->useSetboard) {
5992 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5993 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5994 SendToProgram(message, cps);
5999 int i, j, left=0, right=BOARD_WIDTH;
6000 /* Kludge to set black to move, avoiding the troublesome and now
6001 * deprecated "black" command.
6003 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6004 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6006 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6008 SendToProgram("edit\n", cps);
6009 SendToProgram("#\n", cps);
6010 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6011 bp = &boards[moveNum][i][left];
6012 for (j = left; j < right; j++, bp++) {
6013 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6014 if ((int) *bp < (int) BlackPawn) {
6015 if(j == BOARD_RGHT+1)
6016 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6017 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6018 if(message[0] == '+' || message[0] == '~') {
6019 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6020 PieceToChar((ChessSquare)(DEMOTED *bp)),
6023 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6024 message[1] = BOARD_RGHT - 1 - j + '1';
6025 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6027 SendToProgram(message, cps);
6032 SendToProgram("c\n", cps);
6033 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6034 bp = &boards[moveNum][i][left];
6035 for (j = left; j < right; j++, bp++) {
6036 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6037 if (((int) *bp != (int) EmptySquare)
6038 && ((int) *bp >= (int) BlackPawn)) {
6039 if(j == BOARD_LEFT-2)
6040 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6041 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6043 if(message[0] == '+' || message[0] == '~') {
6044 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6045 PieceToChar((ChessSquare)(DEMOTED *bp)),
6048 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6049 message[1] = BOARD_RGHT - 1 - j + '1';
6050 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6052 SendToProgram(message, cps);
6057 SendToProgram(".\n", cps);
6059 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6063 DefaultPromoChoice (int white)
6066 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6067 result = WhiteFerz; // no choice
6068 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6069 result= WhiteKing; // in Suicide Q is the last thing we want
6070 else if(gameInfo.variant == VariantSpartan)
6071 result = white ? WhiteQueen : WhiteAngel;
6072 else result = WhiteQueen;
6073 if(!white) result = WHITE_TO_BLACK result;
6077 static int autoQueen; // [HGM] oneclick
6080 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6082 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6083 /* [HGM] add Shogi promotions */
6084 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6089 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6090 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6092 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6093 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6096 piece = boards[currentMove][fromY][fromX];
6097 if(gameInfo.variant == VariantShogi) {
6098 promotionZoneSize = BOARD_HEIGHT/3;
6099 highestPromotingPiece = (int)WhiteFerz;
6100 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6101 promotionZoneSize = 3;
6104 // Treat Lance as Pawn when it is not representing Amazon
6105 if(gameInfo.variant != VariantSuper) {
6106 if(piece == WhiteLance) piece = WhitePawn; else
6107 if(piece == BlackLance) piece = BlackPawn;
6110 // next weed out all moves that do not touch the promotion zone at all
6111 if((int)piece >= BlackPawn) {
6112 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6114 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6116 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6117 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6120 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6122 // weed out mandatory Shogi promotions
6123 if(gameInfo.variant == VariantShogi) {
6124 if(piece >= BlackPawn) {
6125 if(toY == 0 && piece == BlackPawn ||
6126 toY == 0 && piece == BlackQueen ||
6127 toY <= 1 && piece == BlackKnight) {
6132 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6133 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6134 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6141 // weed out obviously illegal Pawn moves
6142 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6143 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6144 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6145 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6146 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6147 // note we are not allowed to test for valid (non-)capture, due to premove
6150 // we either have a choice what to promote to, or (in Shogi) whether to promote
6151 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6152 *promoChoice = PieceToChar(BlackFerz); // no choice
6155 // no sense asking what we must promote to if it is going to explode...
6156 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6157 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6160 // give caller the default choice even if we will not make it
6161 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6162 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6163 if( sweepSelect && gameInfo.variant != VariantGreat
6164 && gameInfo.variant != VariantGrand
6165 && gameInfo.variant != VariantSuper) return FALSE;
6166 if(autoQueen) return FALSE; // predetermined
6168 // suppress promotion popup on illegal moves that are not premoves
6169 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6170 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6171 if(appData.testLegality && !premove) {
6172 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6173 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6174 if(moveType != WhitePromotion && moveType != BlackPromotion)
6182 InPalace (int row, int column)
6183 { /* [HGM] for Xiangqi */
6184 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6185 column < (BOARD_WIDTH + 4)/2 &&
6186 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6191 PieceForSquare (int x, int y)
6193 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6196 return boards[currentMove][y][x];
6200 OKToStartUserMove (int x, int y)
6202 ChessSquare from_piece;
6205 if (matchMode) return FALSE;
6206 if (gameMode == EditPosition) return TRUE;
6208 if (x >= 0 && y >= 0)
6209 from_piece = boards[currentMove][y][x];
6211 from_piece = EmptySquare;
6213 if (from_piece == EmptySquare) return FALSE;
6215 white_piece = (int)from_piece >= (int)WhitePawn &&
6216 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6220 case TwoMachinesPlay:
6228 case MachinePlaysWhite:
6229 case IcsPlayingBlack:
6230 if (appData.zippyPlay) return FALSE;
6232 DisplayMoveError(_("You are playing Black"));
6237 case MachinePlaysBlack:
6238 case IcsPlayingWhite:
6239 if (appData.zippyPlay) return FALSE;
6241 DisplayMoveError(_("You are playing White"));
6246 case PlayFromGameFile:
6247 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6249 if (!white_piece && WhiteOnMove(currentMove)) {
6250 DisplayMoveError(_("It is White's turn"));
6253 if (white_piece && !WhiteOnMove(currentMove)) {
6254 DisplayMoveError(_("It is Black's turn"));
6257 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6258 /* Editing correspondence game history */
6259 /* Could disallow this or prompt for confirmation */
6264 case BeginningOfGame:
6265 if (appData.icsActive) return FALSE;
6266 if (!appData.noChessProgram) {
6268 DisplayMoveError(_("You are playing White"));
6275 if (!white_piece && WhiteOnMove(currentMove)) {
6276 DisplayMoveError(_("It is White's turn"));
6279 if (white_piece && !WhiteOnMove(currentMove)) {
6280 DisplayMoveError(_("It is Black's turn"));
6289 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6290 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6291 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6292 && gameMode != AnalyzeFile && gameMode != Training) {
6293 DisplayMoveError(_("Displayed position is not current"));
6300 OnlyMove (int *x, int *y, Boolean captures)
6302 DisambiguateClosure cl;
6303 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6305 case MachinePlaysBlack:
6306 case IcsPlayingWhite:
6307 case BeginningOfGame:
6308 if(!WhiteOnMove(currentMove)) return FALSE;
6310 case MachinePlaysWhite:
6311 case IcsPlayingBlack:
6312 if(WhiteOnMove(currentMove)) return FALSE;
6319 cl.pieceIn = EmptySquare;
6324 cl.promoCharIn = NULLCHAR;
6325 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6326 if( cl.kind == NormalMove ||
6327 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6328 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6329 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6336 if(cl.kind != ImpossibleMove) return FALSE;
6337 cl.pieceIn = EmptySquare;
6342 cl.promoCharIn = NULLCHAR;
6343 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6344 if( cl.kind == NormalMove ||
6345 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6346 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6347 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6358 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6359 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6360 int lastLoadGameUseList = FALSE;
6361 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6362 ChessMove lastLoadGameStart = EndOfFile;
6365 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6368 ChessSquare pdown, pup;
6370 /* Check if the user is playing in turn. This is complicated because we
6371 let the user "pick up" a piece before it is his turn. So the piece he
6372 tried to pick up may have been captured by the time he puts it down!
6373 Therefore we use the color the user is supposed to be playing in this
6374 test, not the color of the piece that is currently on the starting
6375 square---except in EditGame mode, where the user is playing both
6376 sides; fortunately there the capture race can't happen. (It can
6377 now happen in IcsExamining mode, but that's just too bad. The user
6378 will get a somewhat confusing message in that case.)
6383 case TwoMachinesPlay:
6387 /* We switched into a game mode where moves are not accepted,
6388 perhaps while the mouse button was down. */
6391 case MachinePlaysWhite:
6392 /* User is moving for Black */
6393 if (WhiteOnMove(currentMove)) {
6394 DisplayMoveError(_("It is White's turn"));
6399 case MachinePlaysBlack:
6400 /* User is moving for White */
6401 if (!WhiteOnMove(currentMove)) {
6402 DisplayMoveError(_("It is Black's turn"));
6407 case PlayFromGameFile:
6408 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6411 case BeginningOfGame:
6414 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6415 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6416 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6417 /* User is moving for Black */
6418 if (WhiteOnMove(currentMove)) {
6419 DisplayMoveError(_("It is White's turn"));
6423 /* User is moving for White */
6424 if (!WhiteOnMove(currentMove)) {
6425 DisplayMoveError(_("It is Black's turn"));
6431 case IcsPlayingBlack:
6432 /* User is moving for Black */
6433 if (WhiteOnMove(currentMove)) {
6434 if (!appData.premove) {
6435 DisplayMoveError(_("It is White's turn"));
6436 } else if (toX >= 0 && toY >= 0) {
6439 premoveFromX = fromX;
6440 premoveFromY = fromY;
6441 premovePromoChar = promoChar;
6443 if (appData.debugMode)
6444 fprintf(debugFP, "Got premove: fromX %d,"
6445 "fromY %d, toX %d, toY %d\n",
6446 fromX, fromY, toX, toY);
6452 case IcsPlayingWhite:
6453 /* User is moving for White */
6454 if (!WhiteOnMove(currentMove)) {
6455 if (!appData.premove) {
6456 DisplayMoveError(_("It is Black's turn"));
6457 } else if (toX >= 0 && toY >= 0) {
6460 premoveFromX = fromX;
6461 premoveFromY = fromY;
6462 premovePromoChar = promoChar;
6464 if (appData.debugMode)
6465 fprintf(debugFP, "Got premove: fromX %d,"
6466 "fromY %d, toX %d, toY %d\n",
6467 fromX, fromY, toX, toY);
6477 /* EditPosition, empty square, or different color piece;
6478 click-click move is possible */
6479 if (toX == -2 || toY == -2) {
6480 boards[0][fromY][fromX] = EmptySquare;
6481 DrawPosition(FALSE, boards[currentMove]);
6483 } else if (toX >= 0 && toY >= 0) {
6484 boards[0][toY][toX] = boards[0][fromY][fromX];
6485 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6486 if(boards[0][fromY][0] != EmptySquare) {
6487 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6488 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6491 if(fromX == BOARD_RGHT+1) {
6492 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6493 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6494 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6497 boards[0][fromY][fromX] = EmptySquare;
6498 DrawPosition(FALSE, boards[currentMove]);
6504 if(toX < 0 || toY < 0) return;
6505 pdown = boards[currentMove][fromY][fromX];
6506 pup = boards[currentMove][toY][toX];
6508 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6509 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6510 if( pup != EmptySquare ) return;
6511 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6512 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6513 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6514 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6515 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6516 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6517 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6521 /* [HGM] always test for legality, to get promotion info */
6522 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6523 fromY, fromX, toY, toX, promoChar);
6525 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6527 /* [HGM] but possibly ignore an IllegalMove result */
6528 if (appData.testLegality) {
6529 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6530 DisplayMoveError(_("Illegal move"));
6535 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6538 /* Common tail of UserMoveEvent and DropMenuEvent */
6540 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6544 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6545 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6546 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6547 if(WhiteOnMove(currentMove)) {
6548 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6550 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6554 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6555 move type in caller when we know the move is a legal promotion */
6556 if(moveType == NormalMove && promoChar)
6557 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6559 /* [HGM] <popupFix> The following if has been moved here from
6560 UserMoveEvent(). Because it seemed to belong here (why not allow
6561 piece drops in training games?), and because it can only be
6562 performed after it is known to what we promote. */
6563 if (gameMode == Training) {
6564 /* compare the move played on the board to the next move in the
6565 * game. If they match, display the move and the opponent's response.
6566 * If they don't match, display an error message.
6570 CopyBoard(testBoard, boards[currentMove]);
6571 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6573 if (CompareBoards(testBoard, boards[currentMove+1])) {
6574 ForwardInner(currentMove+1);
6576 /* Autoplay the opponent's response.
6577 * if appData.animate was TRUE when Training mode was entered,
6578 * the response will be animated.
6580 saveAnimate = appData.animate;
6581 appData.animate = animateTraining;
6582 ForwardInner(currentMove+1);
6583 appData.animate = saveAnimate;
6585 /* check for the end of the game */
6586 if (currentMove >= forwardMostMove) {
6587 gameMode = PlayFromGameFile;
6589 SetTrainingModeOff();
6590 DisplayInformation(_("End of game"));
6593 DisplayError(_("Incorrect move"), 0);
6598 /* Ok, now we know that the move is good, so we can kill
6599 the previous line in Analysis Mode */
6600 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6601 && currentMove < forwardMostMove) {
6602 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6603 else forwardMostMove = currentMove;
6606 /* If we need the chess program but it's dead, restart it */
6607 ResurrectChessProgram();
6609 /* A user move restarts a paused game*/
6613 thinkOutput[0] = NULLCHAR;
6615 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6617 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6618 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622 if (gameMode == BeginningOfGame) {
6623 if (appData.noChessProgram) {
6624 gameMode = EditGame;
6628 gameMode = MachinePlaysBlack;
6631 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6633 if (first.sendName) {
6634 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6635 SendToProgram(buf, &first);
6642 /* Relay move to ICS or chess engine */
6643 if (appData.icsActive) {
6644 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6645 gameMode == IcsExamining) {
6646 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6647 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6649 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6651 // also send plain move, in case ICS does not understand atomic claims
6652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6656 if (first.sendTime && (gameMode == BeginningOfGame ||
6657 gameMode == MachinePlaysWhite ||
6658 gameMode == MachinePlaysBlack)) {
6659 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6661 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6662 // [HGM] book: if program might be playing, let it use book
6663 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6664 first.maybeThinking = TRUE;
6665 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6666 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6667 SendBoard(&first, currentMove+1);
6668 } else SendMoveToProgram(forwardMostMove-1, &first);
6669 if (currentMove == cmailOldMove + 1) {
6670 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6674 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6678 if(appData.testLegality)
6679 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6685 if (WhiteOnMove(currentMove)) {
6686 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6688 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6692 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6697 case MachinePlaysBlack:
6698 case MachinePlaysWhite:
6699 /* disable certain menu options while machine is thinking */
6700 SetMachineThinkingEnables();
6707 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6708 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6710 if(bookHit) { // [HGM] book: simulate book reply
6711 static char bookMove[MSG_SIZ]; // a bit generous?
6713 programStats.nodes = programStats.depth = programStats.time =
6714 programStats.score = programStats.got_only_move = 0;
6715 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6717 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6718 strcat(bookMove, bookHit);
6719 HandleMachineMove(bookMove, &first);
6725 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6727 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6728 Markers *m = (Markers *) closure;
6729 if(rf == fromY && ff == fromX)
6730 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6731 || kind == WhiteCapturesEnPassant
6732 || kind == BlackCapturesEnPassant);
6733 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6737 MarkTargetSquares (int clear)
6740 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6741 !appData.testLegality || gameMode == EditPosition) return;
6743 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6746 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6747 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6748 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6750 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6753 DrawPosition(TRUE, NULL);
6757 Explode (Board board, int fromX, int fromY, int toX, int toY)
6759 if(gameInfo.variant == VariantAtomic &&
6760 (board[toY][toX] != EmptySquare || // capture?
6761 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6762 board[fromY][fromX] == BlackPawn )
6764 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6770 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6773 CanPromote (ChessSquare piece, int y)
6775 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6776 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6777 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6778 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6779 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6780 gameInfo.variant == VariantMakruk) return FALSE;
6781 return (piece == BlackPawn && y == 1 ||
6782 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6783 piece == BlackLance && y == 1 ||
6784 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6788 LeftClick (ClickType clickType, int xPix, int yPix)
6791 Boolean saveAnimate;
6792 static int second = 0, promotionChoice = 0, clearFlag = 0;
6793 char promoChoice = NULLCHAR;
6796 if(appData.seekGraph && appData.icsActive && loggedOn &&
6797 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6798 SeekGraphClick(clickType, xPix, yPix, 0);
6802 if (clickType == Press) ErrorPopDown();
6804 x = EventToSquare(xPix, BOARD_WIDTH);
6805 y = EventToSquare(yPix, BOARD_HEIGHT);
6806 if (!flipView && y >= 0) {
6807 y = BOARD_HEIGHT - 1 - y;
6809 if (flipView && x >= 0) {
6810 x = BOARD_WIDTH - 1 - x;
6813 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6814 defaultPromoChoice = promoSweep;
6815 promoSweep = EmptySquare; // terminate sweep
6816 promoDefaultAltered = TRUE;
6817 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6820 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6821 if(clickType == Release) return; // ignore upclick of click-click destination
6822 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6823 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6824 if(gameInfo.holdingsWidth &&
6825 (WhiteOnMove(currentMove)
6826 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6827 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6828 // click in right holdings, for determining promotion piece
6829 ChessSquare p = boards[currentMove][y][x];
6830 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6831 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6832 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6833 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6838 DrawPosition(FALSE, boards[currentMove]);
6842 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6843 if(clickType == Press
6844 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6845 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6846 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6849 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6850 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6852 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6853 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6854 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6855 defaultPromoChoice = DefaultPromoChoice(side);
6858 autoQueen = appData.alwaysPromoteToQueen;
6862 gatingPiece = EmptySquare;
6863 if (clickType != Press) {
6864 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6865 DragPieceEnd(xPix, yPix); dragging = 0;
6866 DrawPosition(FALSE, NULL);
6870 fromX = x; fromY = y; toX = toY = -1;
6871 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6872 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6873 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6875 if (OKToStartUserMove(fromX, fromY)) {
6877 MarkTargetSquares(0);
6878 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6879 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6880 promoSweep = defaultPromoChoice;
6881 selectFlag = 0; lastX = xPix; lastY = yPix;
6882 Sweep(0); // Pawn that is going to promote: preview promotion piece
6883 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6885 if (appData.highlightDragging) {
6886 SetHighlights(fromX, fromY, -1, -1);
6888 } else fromX = fromY = -1;
6894 if (clickType == Press && gameMode != EditPosition) {
6899 // ignore off-board to clicks
6900 if(y < 0 || x < 0) return;
6902 /* Check if clicking again on the same color piece */
6903 fromP = boards[currentMove][fromY][fromX];
6904 toP = boards[currentMove][y][x];
6905 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6906 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6907 WhitePawn <= toP && toP <= WhiteKing &&
6908 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6909 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6910 (BlackPawn <= fromP && fromP <= BlackKing &&
6911 BlackPawn <= toP && toP <= BlackKing &&
6912 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6913 !(fromP == BlackKing && toP == BlackRook && frc))) {
6914 /* Clicked again on same color piece -- changed his mind */
6915 second = (x == fromX && y == fromY);
6916 promoDefaultAltered = FALSE;
6917 MarkTargetSquares(1);
6918 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6919 if (appData.highlightDragging) {
6920 SetHighlights(x, y, -1, -1);
6924 if (OKToStartUserMove(x, y)) {
6925 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6926 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6927 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6928 gatingPiece = boards[currentMove][fromY][fromX];
6929 else gatingPiece = EmptySquare;
6931 fromY = y; dragging = 1;
6932 MarkTargetSquares(0);
6933 DragPieceBegin(xPix, yPix, FALSE);
6934 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6935 promoSweep = defaultPromoChoice;
6936 selectFlag = 0; lastX = xPix; lastY = yPix;
6937 Sweep(0); // Pawn that is going to promote: preview promotion piece
6941 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6944 // ignore clicks on holdings
6945 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6948 if (clickType == Release && x == fromX && y == fromY) {
6949 DragPieceEnd(xPix, yPix); dragging = 0;
6951 // a deferred attempt to click-click move an empty square on top of a piece
6952 boards[currentMove][y][x] = EmptySquare;
6954 DrawPosition(FALSE, boards[currentMove]);
6955 fromX = fromY = -1; clearFlag = 0;
6958 if (appData.animateDragging) {
6959 /* Undo animation damage if any */
6960 DrawPosition(FALSE, NULL);
6963 /* Second up/down in same square; just abort move */
6966 gatingPiece = EmptySquare;
6969 ClearPremoveHighlights();
6971 /* First upclick in same square; start click-click mode */
6972 SetHighlights(x, y, -1, -1);
6979 /* we now have a different from- and (possibly off-board) to-square */
6980 /* Completed move */
6983 saveAnimate = appData.animate;
6984 MarkTargetSquares(1);
6985 if (clickType == Press) {
6986 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6987 // must be Edit Position mode with empty-square selected
6988 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6989 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6992 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6993 ChessSquare piece = boards[currentMove][fromY][fromX];
6994 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
6995 promoSweep = defaultPromoChoice;
6996 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
6997 selectFlag = 0; lastX = xPix; lastY = yPix;
6998 Sweep(0); // Pawn that is going to promote: preview promotion piece
6999 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7000 DrawPosition(FALSE, boards[currentMove]);
7003 /* Finish clickclick move */
7004 if (appData.animate || appData.highlightLastMove) {
7005 SetHighlights(fromX, fromY, toX, toY);
7010 /* Finish drag move */
7011 if (appData.highlightLastMove) {
7012 SetHighlights(fromX, fromY, toX, toY);
7016 DragPieceEnd(xPix, yPix); dragging = 0;
7017 /* Don't animate move and drag both */
7018 appData.animate = FALSE;
7021 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7022 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7023 ChessSquare piece = boards[currentMove][fromY][fromX];
7024 if(gameMode == EditPosition && piece != EmptySquare &&
7025 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7028 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7029 n = PieceToNumber(piece - (int)BlackPawn);
7030 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7031 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7032 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7034 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7035 n = PieceToNumber(piece);
7036 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7037 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7038 boards[currentMove][n][BOARD_WIDTH-2]++;
7040 boards[currentMove][fromY][fromX] = EmptySquare;
7044 DrawPosition(TRUE, boards[currentMove]);
7048 // off-board moves should not be highlighted
7049 if(x < 0 || y < 0) ClearHighlights();
7051 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7053 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7054 SetHighlights(fromX, fromY, toX, toY);
7055 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7056 // [HGM] super: promotion to captured piece selected from holdings
7057 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7058 promotionChoice = TRUE;
7059 // kludge follows to temporarily execute move on display, without promoting yet
7060 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7061 boards[currentMove][toY][toX] = p;
7062 DrawPosition(FALSE, boards[currentMove]);
7063 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7064 boards[currentMove][toY][toX] = q;
7065 DisplayMessage("Click in holdings to choose piece", "");
7070 int oldMove = currentMove;
7071 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7072 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7073 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7074 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7075 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7076 DrawPosition(TRUE, boards[currentMove]);
7079 appData.animate = saveAnimate;
7080 if (appData.animate || appData.animateDragging) {
7081 /* Undo animation damage if needed */
7082 DrawPosition(FALSE, NULL);
7087 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7088 { // front-end-free part taken out of PieceMenuPopup
7089 int whichMenu; int xSqr, ySqr;
7091 if(seekGraphUp) { // [HGM] seekgraph
7092 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7093 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7097 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7098 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7099 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7100 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7101 if(action == Press) {
7102 originalFlip = flipView;
7103 flipView = !flipView; // temporarily flip board to see game from partners perspective
7104 DrawPosition(TRUE, partnerBoard);
7105 DisplayMessage(partnerStatus, "");
7107 } else if(action == Release) {
7108 flipView = originalFlip;
7109 DrawPosition(TRUE, boards[currentMove]);
7115 xSqr = EventToSquare(x, BOARD_WIDTH);
7116 ySqr = EventToSquare(y, BOARD_HEIGHT);
7117 if (action == Release) {
7118 if(pieceSweep != EmptySquare) {
7119 EditPositionMenuEvent(pieceSweep, toX, toY);
7120 pieceSweep = EmptySquare;
7121 } else UnLoadPV(); // [HGM] pv
7123 if (action != Press) return -2; // return code to be ignored
7126 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7128 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7129 if (xSqr < 0 || ySqr < 0) return -1;
7130 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7131 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7132 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7133 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7137 if(!appData.icsEngineAnalyze) return -1;
7138 case IcsPlayingWhite:
7139 case IcsPlayingBlack:
7140 if(!appData.zippyPlay) goto noZip;
7143 case MachinePlaysWhite:
7144 case MachinePlaysBlack:
7145 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7146 if (!appData.dropMenu) {
7148 return 2; // flag front-end to grab mouse events
7150 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7151 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7154 if (xSqr < 0 || ySqr < 0) return -1;
7155 if (!appData.dropMenu || appData.testLegality &&
7156 gameInfo.variant != VariantBughouse &&
7157 gameInfo.variant != VariantCrazyhouse) return -1;
7158 whichMenu = 1; // drop menu
7164 if (((*fromX = xSqr) < 0) ||
7165 ((*fromY = ySqr) < 0)) {
7166 *fromX = *fromY = -1;
7170 *fromX = BOARD_WIDTH - 1 - *fromX;
7172 *fromY = BOARD_HEIGHT - 1 - *fromY;
7178 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7180 // char * hint = lastHint;
7181 FrontEndProgramStats stats;
7183 stats.which = cps == &first ? 0 : 1;
7184 stats.depth = cpstats->depth;
7185 stats.nodes = cpstats->nodes;
7186 stats.score = cpstats->score;
7187 stats.time = cpstats->time;
7188 stats.pv = cpstats->movelist;
7189 stats.hint = lastHint;
7190 stats.an_move_index = 0;
7191 stats.an_move_count = 0;
7193 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7194 stats.hint = cpstats->move_name;
7195 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7196 stats.an_move_count = cpstats->nr_moves;
7199 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
7201 SetProgramStats( &stats );
7205 ClearEngineOutputPane (int which)
7207 static FrontEndProgramStats dummyStats;
7208 dummyStats.which = which;
7209 dummyStats.pv = "#";
7210 SetProgramStats( &dummyStats );
7213 #define MAXPLAYERS 500
7216 TourneyStandings (int display)
7218 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7219 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7220 char result, *p, *names[MAXPLAYERS];
7222 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7223 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7224 names[0] = p = strdup(appData.participants);
7225 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7227 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7229 while(result = appData.results[nr]) {
7230 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7231 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7232 wScore = bScore = 0;
7234 case '+': wScore = 2; break;
7235 case '-': bScore = 2; break;
7236 case '=': wScore = bScore = 1; break;
7238 case '*': return strdup("busy"); // tourney not finished
7246 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7247 for(w=0; w<nPlayers; w++) {
7249 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7250 ranking[w] = b; points[w] = bScore; score[b] = -2;
7252 p = malloc(nPlayers*34+1);
7253 for(w=0; w<nPlayers && w<display; w++)
7254 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7260 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7261 { // count all piece types
7263 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7264 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7265 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7268 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7269 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7270 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7271 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7272 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7273 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7278 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7280 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7281 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7283 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7284 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7285 if(myPawns == 2 && nMine == 3) // KPP
7286 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7287 if(myPawns == 1 && nMine == 2) // KP
7288 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7289 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7290 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7291 if(myPawns) return FALSE;
7292 if(pCnt[WhiteRook+side])
7293 return pCnt[BlackRook-side] ||
7294 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7295 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7296 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7297 if(pCnt[WhiteCannon+side]) {
7298 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7299 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7301 if(pCnt[WhiteKnight+side])
7302 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7307 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7309 VariantClass v = gameInfo.variant;
7311 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7312 if(v == VariantShatranj) return TRUE; // always winnable through baring
7313 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7314 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7316 if(v == VariantXiangqi) {
7317 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7319 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7320 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7321 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7322 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7323 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7324 if(stale) // we have at least one last-rank P plus perhaps C
7325 return majors // KPKX
7326 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7328 return pCnt[WhiteFerz+side] // KCAK
7329 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7330 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7331 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7333 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7334 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7336 if(nMine == 1) return FALSE; // bare King
7337 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
7338 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7339 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7340 // by now we have King + 1 piece (or multiple Bishops on the same color)
7341 if(pCnt[WhiteKnight+side])
7342 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7343 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7344 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7346 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7347 if(pCnt[WhiteAlfil+side])
7348 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7349 if(pCnt[WhiteWazir+side])
7350 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7357 CompareWithRights (Board b1, Board b2)
7360 if(!CompareBoards(b1, b2)) return FALSE;
7361 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7362 /* compare castling rights */
7363 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7364 rights++; /* King lost rights, while rook still had them */
7365 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7366 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7367 rights++; /* but at least one rook lost them */
7369 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7371 if( b1[CASTLING][5] != NoRights ) {
7372 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7379 Adjudicate (ChessProgramState *cps)
7380 { // [HGM] some adjudications useful with buggy engines
7381 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7382 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7383 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7384 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7385 int k, count = 0; static int bare = 1;
7386 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7387 Boolean canAdjudicate = !appData.icsActive;
7389 // most tests only when we understand the game, i.e. legality-checking on
7390 if( appData.testLegality )
7391 { /* [HGM] Some more adjudications for obstinate engines */
7392 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7393 static int moveCount = 6;
7395 char *reason = NULL;
7397 /* Count what is on board. */
7398 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7400 /* Some material-based adjudications that have to be made before stalemate test */
7401 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7402 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7403 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7404 if(canAdjudicate && appData.checkMates) {
7406 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7407 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7408 "Xboard adjudication: King destroyed", GE_XBOARD );
7413 /* Bare King in Shatranj (loses) or Losers (wins) */
7414 if( nrW == 1 || nrB == 1) {
7415 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7416 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7417 if(canAdjudicate && appData.checkMates) {
7419 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7420 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7421 "Xboard adjudication: Bare king", GE_XBOARD );
7425 if( gameInfo.variant == VariantShatranj && --bare < 0)
7427 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7428 if(canAdjudicate && appData.checkMates) {
7429 /* but only adjudicate if adjudication enabled */
7431 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7432 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7433 "Xboard adjudication: Bare king", GE_XBOARD );
7440 // don't wait for engine to announce game end if we can judge ourselves
7441 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7443 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7444 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7445 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7446 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7449 reason = "Xboard adjudication: 3rd check";
7450 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7460 reason = "Xboard adjudication: Stalemate";
7461 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7462 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7463 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7464 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7465 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7466 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7467 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7468 EP_CHECKMATE : EP_WINS);
7469 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7470 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7474 reason = "Xboard adjudication: Checkmate";
7475 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7479 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7481 result = GameIsDrawn; break;
7483 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7485 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7489 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7491 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7492 GameEnds( result, reason, GE_XBOARD );
7496 /* Next absolutely insufficient mating material. */
7497 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7498 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7499 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7501 /* always flag draws, for judging claims */
7502 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7504 if(canAdjudicate && appData.materialDraws) {
7505 /* but only adjudicate them if adjudication enabled */
7506 if(engineOpponent) {
7507 SendToProgram("force\n", engineOpponent); // suppress reply
7508 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7510 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7515 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7516 if(gameInfo.variant == VariantXiangqi ?
7517 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7519 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7520 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7521 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7522 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7524 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7525 { /* if the first 3 moves do not show a tactical win, declare draw */
7526 if(engineOpponent) {
7527 SendToProgram("force\n", engineOpponent); // suppress reply
7528 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7530 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7533 } else moveCount = 6;
7535 if (appData.debugMode) { int i;
7536 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7537 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7538 appData.drawRepeats);
7539 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7540 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7544 // Repetition draws and 50-move rule can be applied independently of legality testing
7546 /* Check for rep-draws */
7548 for(k = forwardMostMove-2;
7549 k>=backwardMostMove && k>=forwardMostMove-100 &&
7550 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7551 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7554 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7555 /* compare castling rights */
7556 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7557 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7558 rights++; /* King lost rights, while rook still had them */
7559 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7560 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7561 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7562 rights++; /* but at least one rook lost them */
7564 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7565 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7567 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7568 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7569 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7572 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7573 && appData.drawRepeats > 1) {
7574 /* adjudicate after user-specified nr of repeats */
7575 int result = GameIsDrawn;
7576 char *details = "XBoard adjudication: repetition draw";
7577 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7578 // [HGM] xiangqi: check for forbidden perpetuals
7579 int m, ourPerpetual = 1, hisPerpetual = 1;
7580 for(m=forwardMostMove; m>k; m-=2) {
7581 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7582 ourPerpetual = 0; // the current mover did not always check
7583 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7584 hisPerpetual = 0; // the opponent did not always check
7586 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7587 ourPerpetual, hisPerpetual);
7588 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7589 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7590 details = "Xboard adjudication: perpetual checking";
7592 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7593 break; // (or we would have caught him before). Abort repetition-checking loop.
7595 // Now check for perpetual chases
7596 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7597 hisPerpetual = PerpetualChase(k, forwardMostMove);
7598 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7599 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7600 static char resdet[MSG_SIZ];
7601 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7603 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7605 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7606 break; // Abort repetition-checking loop.
7608 // if neither of us is checking or chasing all the time, or both are, it is draw
7610 if(engineOpponent) {
7611 SendToProgram("force\n", engineOpponent); // suppress reply
7612 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7614 GameEnds( result, details, GE_XBOARD );
7617 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7618 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7622 /* Now we test for 50-move draws. Determine ply count */
7623 count = forwardMostMove;
7624 /* look for last irreversble move */
7625 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7627 /* if we hit starting position, add initial plies */
7628 if( count == backwardMostMove )
7629 count -= initialRulePlies;
7630 count = forwardMostMove - count;
7631 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7632 // adjust reversible move counter for checks in Xiangqi
7633 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7634 if(i < backwardMostMove) i = backwardMostMove;
7635 while(i <= forwardMostMove) {
7636 lastCheck = inCheck; // check evasion does not count
7637 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7638 if(inCheck || lastCheck) count--; // check does not count
7643 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7644 /* this is used to judge if draw claims are legal */
7645 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7646 if(engineOpponent) {
7647 SendToProgram("force\n", engineOpponent); // suppress reply
7648 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7650 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7654 /* if draw offer is pending, treat it as a draw claim
7655 * when draw condition present, to allow engines a way to
7656 * claim draws before making their move to avoid a race
7657 * condition occurring after their move
7659 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7661 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7662 p = "Draw claim: 50-move rule";
7663 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7664 p = "Draw claim: 3-fold repetition";
7665 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7666 p = "Draw claim: insufficient mating material";
7667 if( p != NULL && canAdjudicate) {
7668 if(engineOpponent) {
7669 SendToProgram("force\n", engineOpponent); // suppress reply
7670 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7672 GameEnds( GameIsDrawn, p, GE_XBOARD );
7677 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7678 if(engineOpponent) {
7679 SendToProgram("force\n", engineOpponent); // suppress reply
7680 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7682 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7689 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7690 { // [HGM] book: this routine intercepts moves to simulate book replies
7691 char *bookHit = NULL;
7693 //first determine if the incoming move brings opponent into his book
7694 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7695 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7696 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7697 if(bookHit != NULL && !cps->bookSuspend) {
7698 // make sure opponent is not going to reply after receiving move to book position
7699 SendToProgram("force\n", cps);
7700 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7702 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7703 // now arrange restart after book miss
7705 // after a book hit we never send 'go', and the code after the call to this routine
7706 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7707 char buf[MSG_SIZ], *move = bookHit;
7709 int fromX, fromY, toX, toY;
7713 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7714 &fromX, &fromY, &toX, &toY, &promoChar)) {
7715 (void) CoordsToAlgebraic(boards[forwardMostMove],
7716 PosFlags(forwardMostMove),
7717 fromY, fromX, toY, toX, promoChar, move);
7719 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7723 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7724 SendToProgram(buf, cps);
7725 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7726 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7727 SendToProgram("go\n", cps);
7728 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7729 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7730 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7731 SendToProgram("go\n", cps);
7732 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7734 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7738 ChessProgramState *savedState;
7740 DeferredBookMove (void)
7742 if(savedState->lastPing != savedState->lastPong)
7743 ScheduleDelayedEvent(DeferredBookMove, 10);
7745 HandleMachineMove(savedMessage, savedState);
7748 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7751 HandleMachineMove (char *message, ChessProgramState *cps)
7753 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7754 char realname[MSG_SIZ];
7755 int fromX, fromY, toX, toY;
7762 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7763 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7764 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7765 DisplayError(_("Invalid pairing from pairing engine"), 0);
7768 pairingReceived = 1;
7770 return; // Skim the pairing messages here.
7775 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7777 * Kludge to ignore BEL characters
7779 while (*message == '\007') message++;
7782 * [HGM] engine debug message: ignore lines starting with '#' character
7784 if(cps->debug && *message == '#') return;
7787 * Look for book output
7789 if (cps == &first && bookRequested) {
7790 if (message[0] == '\t' || message[0] == ' ') {
7791 /* Part of the book output is here; append it */
7792 strcat(bookOutput, message);
7793 strcat(bookOutput, " \n");
7795 } else if (bookOutput[0] != NULLCHAR) {
7796 /* All of book output has arrived; display it */
7797 char *p = bookOutput;
7798 while (*p != NULLCHAR) {
7799 if (*p == '\t') *p = ' ';
7802 DisplayInformation(bookOutput);
7803 bookRequested = FALSE;
7804 /* Fall through to parse the current output */
7809 * Look for machine move.
7811 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7812 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7814 /* This method is only useful on engines that support ping */
7815 if (cps->lastPing != cps->lastPong) {
7816 if (gameMode == BeginningOfGame) {
7817 /* Extra move from before last new; ignore */
7818 if (appData.debugMode) {
7819 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7822 if (appData.debugMode) {
7823 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7824 cps->which, gameMode);
7827 SendToProgram("undo\n", cps);
7833 case BeginningOfGame:
7834 /* Extra move from before last reset; ignore */
7835 if (appData.debugMode) {
7836 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7843 /* Extra move after we tried to stop. The mode test is
7844 not a reliable way of detecting this problem, but it's
7845 the best we can do on engines that don't support ping.
7847 if (appData.debugMode) {
7848 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7849 cps->which, gameMode);
7851 SendToProgram("undo\n", cps);
7854 case MachinePlaysWhite:
7855 case IcsPlayingWhite:
7856 machineWhite = TRUE;
7859 case MachinePlaysBlack:
7860 case IcsPlayingBlack:
7861 machineWhite = FALSE;
7864 case TwoMachinesPlay:
7865 machineWhite = (cps->twoMachinesColor[0] == 'w');
7868 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7869 if (appData.debugMode) {
7871 "Ignoring move out of turn by %s, gameMode %d"
7872 ", forwardMost %d\n",
7873 cps->which, gameMode, forwardMostMove);
7878 if (appData.debugMode) { int f = forwardMostMove;
7879 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7880 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7881 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7883 if(cps->alphaRank) AlphaRank(machineMove, 4);
7884 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7885 &fromX, &fromY, &toX, &toY, &promoChar)) {
7886 /* Machine move could not be parsed; ignore it. */
7887 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7888 machineMove, _(cps->which));
7889 DisplayError(buf1, 0);
7890 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7891 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7892 if (gameMode == TwoMachinesPlay) {
7893 GameEnds(machineWhite ? BlackWins : WhiteWins,
7899 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7900 /* So we have to redo legality test with true e.p. status here, */
7901 /* to make sure an illegal e.p. capture does not slip through, */
7902 /* to cause a forfeit on a justified illegal-move complaint */
7903 /* of the opponent. */
7904 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7906 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7907 fromY, fromX, toY, toX, promoChar);
7908 if (appData.debugMode) {
7910 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7911 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7912 fprintf(debugFP, "castling rights\n");
7914 if(moveType == IllegalMove) {
7915 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7916 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7917 GameEnds(machineWhite ? BlackWins : WhiteWins,
7920 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7921 /* [HGM] Kludge to handle engines that send FRC-style castling
7922 when they shouldn't (like TSCP-Gothic) */
7924 case WhiteASideCastleFR:
7925 case BlackASideCastleFR:
7927 currentMoveString[2]++;
7929 case WhiteHSideCastleFR:
7930 case BlackHSideCastleFR:
7932 currentMoveString[2]--;
7934 default: ; // nothing to do, but suppresses warning of pedantic compilers
7937 hintRequested = FALSE;
7938 lastHint[0] = NULLCHAR;
7939 bookRequested = FALSE;
7940 /* Program may be pondering now */
7941 cps->maybeThinking = TRUE;
7942 if (cps->sendTime == 2) cps->sendTime = 1;
7943 if (cps->offeredDraw) cps->offeredDraw--;
7945 /* [AS] Save move info*/
7946 pvInfoList[ forwardMostMove ].score = programStats.score;
7947 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7948 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7950 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7952 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7953 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7956 while( count < adjudicateLossPlies ) {
7957 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7960 score = -score; /* Flip score for winning side */
7963 if( score > adjudicateLossThreshold ) {
7970 if( count >= adjudicateLossPlies ) {
7971 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7973 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7974 "Xboard adjudication",
7981 if(Adjudicate(cps)) {
7982 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7983 return; // [HGM] adjudicate: for all automatic game ends
7987 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7989 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7990 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7992 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7994 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7996 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7997 char buf[3*MSG_SIZ];
7999 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8000 programStats.score / 100.,
8002 programStats.time / 100.,
8003 (unsigned int)programStats.nodes,
8004 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8005 programStats.movelist);
8007 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8012 /* [AS] Clear stats for next move */
8013 ClearProgramStats();
8014 thinkOutput[0] = NULLCHAR;
8015 hiddenThinkOutputState = 0;
8018 if (gameMode == TwoMachinesPlay) {
8019 /* [HGM] relaying draw offers moved to after reception of move */
8020 /* and interpreting offer as claim if it brings draw condition */
8021 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8022 SendToProgram("draw\n", cps->other);
8024 if (cps->other->sendTime) {
8025 SendTimeRemaining(cps->other,
8026 cps->other->twoMachinesColor[0] == 'w');
8028 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8029 if (firstMove && !bookHit) {
8031 if (cps->other->useColors) {
8032 SendToProgram(cps->other->twoMachinesColor, cps->other);
8034 SendToProgram("go\n", cps->other);
8036 cps->other->maybeThinking = TRUE;
8039 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8041 if (!pausing && appData.ringBellAfterMoves) {
8046 * Reenable menu items that were disabled while
8047 * machine was thinking
8049 if (gameMode != TwoMachinesPlay)
8050 SetUserThinkingEnables();
8052 // [HGM] book: after book hit opponent has received move and is now in force mode
8053 // force the book reply into it, and then fake that it outputted this move by jumping
8054 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8056 static char bookMove[MSG_SIZ]; // a bit generous?
8058 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8059 strcat(bookMove, bookHit);
8062 programStats.nodes = programStats.depth = programStats.time =
8063 programStats.score = programStats.got_only_move = 0;
8064 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8066 if(cps->lastPing != cps->lastPong) {
8067 savedMessage = message; // args for deferred call
8069 ScheduleDelayedEvent(DeferredBookMove, 10);
8078 /* Set special modes for chess engines. Later something general
8079 * could be added here; for now there is just one kludge feature,
8080 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8081 * when "xboard" is given as an interactive command.
8083 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8084 cps->useSigint = FALSE;
8085 cps->useSigterm = FALSE;
8087 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8088 ParseFeatures(message+8, cps);
8089 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8092 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8093 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8094 int dummy, s=6; char buf[MSG_SIZ];
8095 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8096 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8097 if(startedFromSetupPosition) return;
8098 ParseFEN(boards[0], &dummy, message+s);
8099 DrawPosition(TRUE, boards[0]);
8100 startedFromSetupPosition = TRUE;
8103 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8104 * want this, I was asked to put it in, and obliged.
8106 if (!strncmp(message, "setboard ", 9)) {
8107 Board initial_position;
8109 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8111 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8112 DisplayError(_("Bad FEN received from engine"), 0);
8116 CopyBoard(boards[0], initial_position);
8117 initialRulePlies = FENrulePlies;
8118 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8119 else gameMode = MachinePlaysBlack;
8120 DrawPosition(FALSE, boards[currentMove]);
8126 * Look for communication commands
8128 if (!strncmp(message, "telluser ", 9)) {
8129 if(message[9] == '\\' && message[10] == '\\')
8130 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8132 DisplayNote(message + 9);
8135 if (!strncmp(message, "tellusererror ", 14)) {
8137 if(message[14] == '\\' && message[15] == '\\')
8138 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8140 DisplayError(message + 14, 0);
8143 if (!strncmp(message, "tellopponent ", 13)) {
8144 if (appData.icsActive) {
8146 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8150 DisplayNote(message + 13);
8154 if (!strncmp(message, "tellothers ", 11)) {
8155 if (appData.icsActive) {
8157 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8163 if (!strncmp(message, "tellall ", 8)) {
8164 if (appData.icsActive) {
8166 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8170 DisplayNote(message + 8);
8174 if (strncmp(message, "warning", 7) == 0) {
8175 /* Undocumented feature, use tellusererror in new code */
8176 DisplayError(message, 0);
8179 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8180 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8181 strcat(realname, " query");
8182 AskQuestion(realname, buf2, buf1, cps->pr);
8185 /* Commands from the engine directly to ICS. We don't allow these to be
8186 * sent until we are logged on. Crafty kibitzes have been known to
8187 * interfere with the login process.
8190 if (!strncmp(message, "tellics ", 8)) {
8191 SendToICS(message + 8);
8195 if (!strncmp(message, "tellicsnoalias ", 15)) {
8196 SendToICS(ics_prefix);
8197 SendToICS(message + 15);
8201 /* The following are for backward compatibility only */
8202 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8203 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8204 SendToICS(ics_prefix);
8210 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8214 * If the move is illegal, cancel it and redraw the board.
8215 * Also deal with other error cases. Matching is rather loose
8216 * here to accommodate engines written before the spec.
8218 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8219 strncmp(message, "Error", 5) == 0) {
8220 if (StrStr(message, "name") ||
8221 StrStr(message, "rating") || StrStr(message, "?") ||
8222 StrStr(message, "result") || StrStr(message, "board") ||
8223 StrStr(message, "bk") || StrStr(message, "computer") ||
8224 StrStr(message, "variant") || StrStr(message, "hint") ||
8225 StrStr(message, "random") || StrStr(message, "depth") ||
8226 StrStr(message, "accepted")) {
8229 if (StrStr(message, "protover")) {
8230 /* Program is responding to input, so it's apparently done
8231 initializing, and this error message indicates it is
8232 protocol version 1. So we don't need to wait any longer
8233 for it to initialize and send feature commands. */
8234 FeatureDone(cps, 1);
8235 cps->protocolVersion = 1;
8238 cps->maybeThinking = FALSE;
8240 if (StrStr(message, "draw")) {
8241 /* Program doesn't have "draw" command */
8242 cps->sendDrawOffers = 0;
8245 if (cps->sendTime != 1 &&
8246 (StrStr(message, "time") || StrStr(message, "otim"))) {
8247 /* Program apparently doesn't have "time" or "otim" command */
8251 if (StrStr(message, "analyze")) {
8252 cps->analysisSupport = FALSE;
8253 cps->analyzing = FALSE;
8254 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8255 EditGameEvent(); // [HGM] try to preserve loaded game
8256 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8257 DisplayError(buf2, 0);
8260 if (StrStr(message, "(no matching move)st")) {
8261 /* Special kludge for GNU Chess 4 only */
8262 cps->stKludge = TRUE;
8263 SendTimeControl(cps, movesPerSession, timeControl,
8264 timeIncrement, appData.searchDepth,
8268 if (StrStr(message, "(no matching move)sd")) {
8269 /* Special kludge for GNU Chess 4 only */
8270 cps->sdKludge = TRUE;
8271 SendTimeControl(cps, movesPerSession, timeControl,
8272 timeIncrement, appData.searchDepth,
8276 if (!StrStr(message, "llegal")) {
8279 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8280 gameMode == IcsIdle) return;
8281 if (forwardMostMove <= backwardMostMove) return;
8282 if (pausing) PauseEvent();
8283 if(appData.forceIllegal) {
8284 // [HGM] illegal: machine refused move; force position after move into it
8285 SendToProgram("force\n", cps);
8286 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8287 // we have a real problem now, as SendBoard will use the a2a3 kludge
8288 // when black is to move, while there might be nothing on a2 or black
8289 // might already have the move. So send the board as if white has the move.
8290 // But first we must change the stm of the engine, as it refused the last move
8291 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8292 if(WhiteOnMove(forwardMostMove)) {
8293 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8294 SendBoard(cps, forwardMostMove); // kludgeless board
8296 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8297 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8298 SendBoard(cps, forwardMostMove+1); // kludgeless board
8300 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8301 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8302 gameMode == TwoMachinesPlay)
8303 SendToProgram("go\n", cps);
8306 if (gameMode == PlayFromGameFile) {
8307 /* Stop reading this game file */
8308 gameMode = EditGame;
8311 /* [HGM] illegal-move claim should forfeit game when Xboard */
8312 /* only passes fully legal moves */
8313 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8314 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8315 "False illegal-move claim", GE_XBOARD );
8316 return; // do not take back move we tested as valid
8318 currentMove = forwardMostMove-1;
8319 DisplayMove(currentMove-1); /* before DisplayMoveError */
8320 SwitchClocks(forwardMostMove-1); // [HGM] race
8321 DisplayBothClocks();
8322 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8323 parseList[currentMove], _(cps->which));
8324 DisplayMoveError(buf1);
8325 DrawPosition(FALSE, boards[currentMove]);
8327 SetUserThinkingEnables();
8330 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8331 /* Program has a broken "time" command that
8332 outputs a string not ending in newline.
8338 * If chess program startup fails, exit with an error message.
8339 * Attempts to recover here are futile.
8341 if ((StrStr(message, "unknown host") != NULL)
8342 || (StrStr(message, "No remote directory") != NULL)
8343 || (StrStr(message, "not found") != NULL)
8344 || (StrStr(message, "No such file") != NULL)
8345 || (StrStr(message, "can't alloc") != NULL)
8346 || (StrStr(message, "Permission denied") != NULL)) {
8348 cps->maybeThinking = FALSE;
8349 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8350 _(cps->which), cps->program, cps->host, message);
8351 RemoveInputSource(cps->isr);
8352 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8353 if(cps == &first) appData.noChessProgram = TRUE;
8354 DisplayError(buf1, 0);
8360 * Look for hint output
8362 if (sscanf(message, "Hint: %s", buf1) == 1) {
8363 if (cps == &first && hintRequested) {
8364 hintRequested = FALSE;
8365 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8366 &fromX, &fromY, &toX, &toY, &promoChar)) {
8367 (void) CoordsToAlgebraic(boards[forwardMostMove],
8368 PosFlags(forwardMostMove),
8369 fromY, fromX, toY, toX, promoChar, buf1);
8370 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8371 DisplayInformation(buf2);
8373 /* Hint move could not be parsed!? */
8374 snprintf(buf2, sizeof(buf2),
8375 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8376 buf1, _(cps->which));
8377 DisplayError(buf2, 0);
8380 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8386 * Ignore other messages if game is not in progress
8388 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8389 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8392 * look for win, lose, draw, or draw offer
8394 if (strncmp(message, "1-0", 3) == 0) {
8395 char *p, *q, *r = "";
8396 p = strchr(message, '{');
8404 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8406 } else if (strncmp(message, "0-1", 3) == 0) {
8407 char *p, *q, *r = "";
8408 p = strchr(message, '{');
8416 /* Kludge for Arasan 4.1 bug */
8417 if (strcmp(r, "Black resigns") == 0) {
8418 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8421 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8423 } else if (strncmp(message, "1/2", 3) == 0) {
8424 char *p, *q, *r = "";
8425 p = strchr(message, '{');
8434 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8437 } else if (strncmp(message, "White resign", 12) == 0) {
8438 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8440 } else if (strncmp(message, "Black resign", 12) == 0) {
8441 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8443 } else if (strncmp(message, "White matches", 13) == 0 ||
8444 strncmp(message, "Black matches", 13) == 0 ) {
8445 /* [HGM] ignore GNUShogi noises */
8447 } else if (strncmp(message, "White", 5) == 0 &&
8448 message[5] != '(' &&
8449 StrStr(message, "Black") == NULL) {
8450 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8452 } else if (strncmp(message, "Black", 5) == 0 &&
8453 message[5] != '(') {
8454 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8456 } else if (strcmp(message, "resign") == 0 ||
8457 strcmp(message, "computer resigns") == 0) {
8459 case MachinePlaysBlack:
8460 case IcsPlayingBlack:
8461 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8463 case MachinePlaysWhite:
8464 case IcsPlayingWhite:
8465 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8467 case TwoMachinesPlay:
8468 if (cps->twoMachinesColor[0] == 'w')
8469 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8471 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8478 } else if (strncmp(message, "opponent mates", 14) == 0) {
8480 case MachinePlaysBlack:
8481 case IcsPlayingBlack:
8482 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8484 case MachinePlaysWhite:
8485 case IcsPlayingWhite:
8486 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8488 case TwoMachinesPlay:
8489 if (cps->twoMachinesColor[0] == 'w')
8490 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8492 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8499 } else if (strncmp(message, "computer mates", 14) == 0) {
8501 case MachinePlaysBlack:
8502 case IcsPlayingBlack:
8503 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8505 case MachinePlaysWhite:
8506 case IcsPlayingWhite:
8507 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8509 case TwoMachinesPlay:
8510 if (cps->twoMachinesColor[0] == 'w')
8511 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8513 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8520 } else if (strncmp(message, "checkmate", 9) == 0) {
8521 if (WhiteOnMove(forwardMostMove)) {
8522 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8524 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527 } else if (strstr(message, "Draw") != NULL ||
8528 strstr(message, "game is a draw") != NULL) {
8529 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8531 } else if (strstr(message, "offer") != NULL &&
8532 strstr(message, "draw") != NULL) {
8534 if (appData.zippyPlay && first.initDone) {
8535 /* Relay offer to ICS */
8536 SendToICS(ics_prefix);
8537 SendToICS("draw\n");
8540 cps->offeredDraw = 2; /* valid until this engine moves twice */
8541 if (gameMode == TwoMachinesPlay) {
8542 if (cps->other->offeredDraw) {
8543 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8544 /* [HGM] in two-machine mode we delay relaying draw offer */
8545 /* until after we also have move, to see if it is really claim */
8547 } else if (gameMode == MachinePlaysWhite ||
8548 gameMode == MachinePlaysBlack) {
8549 if (userOfferedDraw) {
8550 DisplayInformation(_("Machine accepts your draw offer"));
8551 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8553 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8560 * Look for thinking output
8562 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8563 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8565 int plylev, mvleft, mvtot, curscore, time;
8566 char mvname[MOVE_LEN];
8570 int prefixHint = FALSE;
8571 mvname[0] = NULLCHAR;
8574 case MachinePlaysBlack:
8575 case IcsPlayingBlack:
8576 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8578 case MachinePlaysWhite:
8579 case IcsPlayingWhite:
8580 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8585 case IcsObserving: /* [DM] icsEngineAnalyze */
8586 if (!appData.icsEngineAnalyze) ignore = TRUE;
8588 case TwoMachinesPlay:
8589 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8599 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8601 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8602 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8604 if (plyext != ' ' && plyext != '\t') {
8608 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8609 if( cps->scoreIsAbsolute &&
8610 ( gameMode == MachinePlaysBlack ||
8611 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8612 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8613 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8614 !WhiteOnMove(currentMove)
8617 curscore = -curscore;
8620 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8622 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8625 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8626 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8627 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8628 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8629 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8630 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8632 } else DisplayError(_("failed writing PV"), 0);
8635 tempStats.depth = plylev;
8636 tempStats.nodes = nodes;
8637 tempStats.time = time;
8638 tempStats.score = curscore;
8639 tempStats.got_only_move = 0;
8641 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8644 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8645 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8646 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8647 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8648 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8649 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8650 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8651 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8654 /* Buffer overflow protection */
8655 if (pv[0] != NULLCHAR) {
8656 if (strlen(pv) >= sizeof(tempStats.movelist)
8657 && appData.debugMode) {
8659 "PV is too long; using the first %u bytes.\n",
8660 (unsigned) sizeof(tempStats.movelist) - 1);
8663 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8665 sprintf(tempStats.movelist, " no PV\n");
8668 if (tempStats.seen_stat) {
8669 tempStats.ok_to_send = 1;
8672 if (strchr(tempStats.movelist, '(') != NULL) {
8673 tempStats.line_is_book = 1;
8674 tempStats.nr_moves = 0;
8675 tempStats.moves_left = 0;
8677 tempStats.line_is_book = 0;
8680 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8681 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8683 SendProgramStatsToFrontend( cps, &tempStats );
8686 [AS] Protect the thinkOutput buffer from overflow... this
8687 is only useful if buf1 hasn't overflowed first!
8689 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8691 (gameMode == TwoMachinesPlay ?
8692 ToUpper(cps->twoMachinesColor[0]) : ' '),
8693 ((double) curscore) / 100.0,
8694 prefixHint ? lastHint : "",
8695 prefixHint ? " " : "" );
8697 if( buf1[0] != NULLCHAR ) {
8698 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8700 if( strlen(pv) > max_len ) {
8701 if( appData.debugMode) {
8702 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8704 pv[max_len+1] = '\0';
8707 strcat( thinkOutput, pv);
8710 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8711 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8712 DisplayMove(currentMove - 1);
8716 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8717 /* crafty (9.25+) says "(only move) <move>"
8718 * if there is only 1 legal move
8720 sscanf(p, "(only move) %s", buf1);
8721 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8722 sprintf(programStats.movelist, "%s (only move)", buf1);
8723 programStats.depth = 1;
8724 programStats.nr_moves = 1;
8725 programStats.moves_left = 1;
8726 programStats.nodes = 1;
8727 programStats.time = 1;
8728 programStats.got_only_move = 1;
8730 /* Not really, but we also use this member to
8731 mean "line isn't going to change" (Crafty
8732 isn't searching, so stats won't change) */
8733 programStats.line_is_book = 1;
8735 SendProgramStatsToFrontend( cps, &programStats );
8737 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8738 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739 DisplayMove(currentMove - 1);
8742 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8743 &time, &nodes, &plylev, &mvleft,
8744 &mvtot, mvname) >= 5) {
8745 /* The stat01: line is from Crafty (9.29+) in response
8746 to the "." command */
8747 programStats.seen_stat = 1;
8748 cps->maybeThinking = TRUE;
8750 if (programStats.got_only_move || !appData.periodicUpdates)
8753 programStats.depth = plylev;
8754 programStats.time = time;
8755 programStats.nodes = nodes;
8756 programStats.moves_left = mvleft;
8757 programStats.nr_moves = mvtot;
8758 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8759 programStats.ok_to_send = 1;
8760 programStats.movelist[0] = '\0';
8762 SendProgramStatsToFrontend( cps, &programStats );
8766 } else if (strncmp(message,"++",2) == 0) {
8767 /* Crafty 9.29+ outputs this */
8768 programStats.got_fail = 2;
8771 } else if (strncmp(message,"--",2) == 0) {
8772 /* Crafty 9.29+ outputs this */
8773 programStats.got_fail = 1;
8776 } else if (thinkOutput[0] != NULLCHAR &&
8777 strncmp(message, " ", 4) == 0) {
8778 unsigned message_len;
8781 while (*p && *p == ' ') p++;
8783 message_len = strlen( p );
8785 /* [AS] Avoid buffer overflow */
8786 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8787 strcat(thinkOutput, " ");
8788 strcat(thinkOutput, p);
8791 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8792 strcat(programStats.movelist, " ");
8793 strcat(programStats.movelist, p);
8796 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8797 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8798 DisplayMove(currentMove - 1);
8806 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8807 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8809 ChessProgramStats cpstats;
8811 if (plyext != ' ' && plyext != '\t') {
8815 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8816 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8817 curscore = -curscore;
8820 cpstats.depth = plylev;
8821 cpstats.nodes = nodes;
8822 cpstats.time = time;
8823 cpstats.score = curscore;
8824 cpstats.got_only_move = 0;
8825 cpstats.movelist[0] = '\0';
8827 if (buf1[0] != NULLCHAR) {
8828 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8831 cpstats.ok_to_send = 0;
8832 cpstats.line_is_book = 0;
8833 cpstats.nr_moves = 0;
8834 cpstats.moves_left = 0;
8836 SendProgramStatsToFrontend( cps, &cpstats );
8843 /* Parse a game score from the character string "game", and
8844 record it as the history of the current game. The game
8845 score is NOT assumed to start from the standard position.
8846 The display is not updated in any way.
8849 ParseGameHistory (char *game)
8852 int fromX, fromY, toX, toY, boardIndex;
8857 if (appData.debugMode)
8858 fprintf(debugFP, "Parsing game history: %s\n", game);
8860 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8861 gameInfo.site = StrSave(appData.icsHost);
8862 gameInfo.date = PGNDate();
8863 gameInfo.round = StrSave("-");
8865 /* Parse out names of players */
8866 while (*game == ' ') game++;
8868 while (*game != ' ') *p++ = *game++;
8870 gameInfo.white = StrSave(buf);
8871 while (*game == ' ') game++;
8873 while (*game != ' ' && *game != '\n') *p++ = *game++;
8875 gameInfo.black = StrSave(buf);
8878 boardIndex = blackPlaysFirst ? 1 : 0;
8881 yyboardindex = boardIndex;
8882 moveType = (ChessMove) Myylex();
8884 case IllegalMove: /* maybe suicide chess, etc. */
8885 if (appData.debugMode) {
8886 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8887 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8888 setbuf(debugFP, NULL);
8890 case WhitePromotion:
8891 case BlackPromotion:
8892 case WhiteNonPromotion:
8893 case BlackNonPromotion:
8895 case WhiteCapturesEnPassant:
8896 case BlackCapturesEnPassant:
8897 case WhiteKingSideCastle:
8898 case WhiteQueenSideCastle:
8899 case BlackKingSideCastle:
8900 case BlackQueenSideCastle:
8901 case WhiteKingSideCastleWild:
8902 case WhiteQueenSideCastleWild:
8903 case BlackKingSideCastleWild:
8904 case BlackQueenSideCastleWild:
8906 case WhiteHSideCastleFR:
8907 case WhiteASideCastleFR:
8908 case BlackHSideCastleFR:
8909 case BlackASideCastleFR:
8911 fromX = currentMoveString[0] - AAA;
8912 fromY = currentMoveString[1] - ONE;
8913 toX = currentMoveString[2] - AAA;
8914 toY = currentMoveString[3] - ONE;
8915 promoChar = currentMoveString[4];
8919 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8920 fromX = moveType == WhiteDrop ?
8921 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8922 (int) CharToPiece(ToLower(currentMoveString[0]));
8924 toX = currentMoveString[2] - AAA;
8925 toY = currentMoveString[3] - ONE;
8926 promoChar = NULLCHAR;
8930 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8931 if (appData.debugMode) {
8932 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8933 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8934 setbuf(debugFP, NULL);
8936 DisplayError(buf, 0);
8938 case ImpossibleMove:
8940 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8941 if (appData.debugMode) {
8942 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8943 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8944 setbuf(debugFP, NULL);
8946 DisplayError(buf, 0);
8949 if (boardIndex < backwardMostMove) {
8950 /* Oops, gap. How did that happen? */
8951 DisplayError(_("Gap in move list"), 0);
8954 backwardMostMove = blackPlaysFirst ? 1 : 0;
8955 if (boardIndex > forwardMostMove) {
8956 forwardMostMove = boardIndex;
8960 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8961 strcat(parseList[boardIndex-1], " ");
8962 strcat(parseList[boardIndex-1], yy_text);
8974 case GameUnfinished:
8975 if (gameMode == IcsExamining) {
8976 if (boardIndex < backwardMostMove) {
8977 /* Oops, gap. How did that happen? */
8980 backwardMostMove = blackPlaysFirst ? 1 : 0;
8983 gameInfo.result = moveType;
8984 p = strchr(yy_text, '{');
8985 if (p == NULL) p = strchr(yy_text, '(');
8988 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8990 q = strchr(p, *p == '{' ? '}' : ')');
8991 if (q != NULL) *q = NULLCHAR;
8994 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8995 gameInfo.resultDetails = StrSave(p);
8998 if (boardIndex >= forwardMostMove &&
8999 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9000 backwardMostMove = blackPlaysFirst ? 1 : 0;
9003 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9004 fromY, fromX, toY, toX, promoChar,
9005 parseList[boardIndex]);
9006 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9007 /* currentMoveString is set as a side-effect of yylex */
9008 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9009 strcat(moveList[boardIndex], "\n");
9011 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9012 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9018 if(gameInfo.variant != VariantShogi)
9019 strcat(parseList[boardIndex - 1], "+");
9023 strcat(parseList[boardIndex - 1], "#");
9030 /* Apply a move to the given board */
9032 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9034 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9035 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9037 /* [HGM] compute & store e.p. status and castling rights for new position */
9038 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9040 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9041 oldEP = (signed char)board[EP_STATUS];
9042 board[EP_STATUS] = EP_NONE;
9044 if (fromY == DROP_RANK) {
9046 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9047 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9050 piece = board[toY][toX] = (ChessSquare) fromX;
9054 if( board[toY][toX] != EmptySquare )
9055 board[EP_STATUS] = EP_CAPTURE;
9057 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9058 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9059 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9061 if( board[fromY][fromX] == WhitePawn ) {
9062 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9063 board[EP_STATUS] = EP_PAWN_MOVE;
9065 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9066 gameInfo.variant != VariantBerolina || toX < fromX)
9067 board[EP_STATUS] = toX | berolina;
9068 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9069 gameInfo.variant != VariantBerolina || toX > fromX)
9070 board[EP_STATUS] = toX;
9073 if( board[fromY][fromX] == BlackPawn ) {
9074 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9075 board[EP_STATUS] = EP_PAWN_MOVE;
9076 if( toY-fromY== -2) {
9077 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9078 gameInfo.variant != VariantBerolina || toX < fromX)
9079 board[EP_STATUS] = toX | berolina;
9080 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9081 gameInfo.variant != VariantBerolina || toX > fromX)
9082 board[EP_STATUS] = toX;
9086 for(i=0; i<nrCastlingRights; i++) {
9087 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9088 board[CASTLING][i] == toX && castlingRank[i] == toY
9089 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9092 if (fromX == toX && fromY == toY) return;
9094 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9095 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9096 if(gameInfo.variant == VariantKnightmate)
9097 king += (int) WhiteUnicorn - (int) WhiteKing;
9099 /* Code added by Tord: */
9100 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9101 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9102 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9103 board[fromY][fromX] = EmptySquare;
9104 board[toY][toX] = EmptySquare;
9105 if((toX > fromX) != (piece == WhiteRook)) {
9106 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9108 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9110 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9111 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9112 board[fromY][fromX] = EmptySquare;
9113 board[toY][toX] = EmptySquare;
9114 if((toX > fromX) != (piece == BlackRook)) {
9115 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9117 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9119 /* End of code added by Tord */
9121 } else if (board[fromY][fromX] == king
9122 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9123 && toY == fromY && toX > fromX+1) {
9124 board[fromY][fromX] = EmptySquare;
9125 board[toY][toX] = king;
9126 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9127 board[fromY][BOARD_RGHT-1] = EmptySquare;
9128 } else if (board[fromY][fromX] == king
9129 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130 && toY == fromY && toX < fromX-1) {
9131 board[fromY][fromX] = EmptySquare;
9132 board[toY][toX] = king;
9133 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9134 board[fromY][BOARD_LEFT] = EmptySquare;
9135 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9136 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9137 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9139 /* white pawn promotion */
9140 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9141 if(gameInfo.variant==VariantBughouse ||
9142 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9143 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9144 board[fromY][fromX] = EmptySquare;
9145 } else if ((fromY >= BOARD_HEIGHT>>1)
9147 && gameInfo.variant != VariantXiangqi
9148 && gameInfo.variant != VariantBerolina
9149 && (board[fromY][fromX] == WhitePawn)
9150 && (board[toY][toX] == EmptySquare)) {
9151 board[fromY][fromX] = EmptySquare;
9152 board[toY][toX] = WhitePawn;
9153 captured = board[toY - 1][toX];
9154 board[toY - 1][toX] = EmptySquare;
9155 } else if ((fromY == BOARD_HEIGHT-4)
9157 && gameInfo.variant == VariantBerolina
9158 && (board[fromY][fromX] == WhitePawn)
9159 && (board[toY][toX] == EmptySquare)) {
9160 board[fromY][fromX] = EmptySquare;
9161 board[toY][toX] = WhitePawn;
9162 if(oldEP & EP_BEROLIN_A) {
9163 captured = board[fromY][fromX-1];
9164 board[fromY][fromX-1] = EmptySquare;
9165 }else{ captured = board[fromY][fromX+1];
9166 board[fromY][fromX+1] = EmptySquare;
9168 } else if (board[fromY][fromX] == king
9169 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9170 && toY == fromY && toX > fromX+1) {
9171 board[fromY][fromX] = EmptySquare;
9172 board[toY][toX] = king;
9173 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9174 board[fromY][BOARD_RGHT-1] = EmptySquare;
9175 } else if (board[fromY][fromX] == king
9176 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9177 && toY == fromY && toX < fromX-1) {
9178 board[fromY][fromX] = EmptySquare;
9179 board[toY][toX] = king;
9180 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9181 board[fromY][BOARD_LEFT] = EmptySquare;
9182 } else if (fromY == 7 && fromX == 3
9183 && board[fromY][fromX] == BlackKing
9184 && toY == 7 && toX == 5) {
9185 board[fromY][fromX] = EmptySquare;
9186 board[toY][toX] = BlackKing;
9187 board[fromY][7] = EmptySquare;
9188 board[toY][4] = BlackRook;
9189 } else if (fromY == 7 && fromX == 3
9190 && board[fromY][fromX] == BlackKing
9191 && toY == 7 && toX == 1) {
9192 board[fromY][fromX] = EmptySquare;
9193 board[toY][toX] = BlackKing;
9194 board[fromY][0] = EmptySquare;
9195 board[toY][2] = BlackRook;
9196 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9197 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9198 && toY < promoRank && promoChar
9200 /* black pawn promotion */
9201 board[toY][toX] = CharToPiece(ToLower(promoChar));
9202 if(gameInfo.variant==VariantBughouse ||
9203 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9204 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9205 board[fromY][fromX] = EmptySquare;
9206 } else if ((fromY < BOARD_HEIGHT>>1)
9208 && gameInfo.variant != VariantXiangqi
9209 && gameInfo.variant != VariantBerolina
9210 && (board[fromY][fromX] == BlackPawn)
9211 && (board[toY][toX] == EmptySquare)) {
9212 board[fromY][fromX] = EmptySquare;
9213 board[toY][toX] = BlackPawn;
9214 captured = board[toY + 1][toX];
9215 board[toY + 1][toX] = EmptySquare;
9216 } else if ((fromY == 3)
9218 && gameInfo.variant == VariantBerolina
9219 && (board[fromY][fromX] == BlackPawn)
9220 && (board[toY][toX] == EmptySquare)) {
9221 board[fromY][fromX] = EmptySquare;
9222 board[toY][toX] = BlackPawn;
9223 if(oldEP & EP_BEROLIN_A) {
9224 captured = board[fromY][fromX-1];
9225 board[fromY][fromX-1] = EmptySquare;
9226 }else{ captured = board[fromY][fromX+1];
9227 board[fromY][fromX+1] = EmptySquare;
9230 board[toY][toX] = board[fromY][fromX];
9231 board[fromY][fromX] = EmptySquare;
9235 if (gameInfo.holdingsWidth != 0) {
9237 /* !!A lot more code needs to be written to support holdings */
9238 /* [HGM] OK, so I have written it. Holdings are stored in the */
9239 /* penultimate board files, so they are automaticlly stored */
9240 /* in the game history. */
9241 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9242 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9243 /* Delete from holdings, by decreasing count */
9244 /* and erasing image if necessary */
9245 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9246 if(p < (int) BlackPawn) { /* white drop */
9247 p -= (int)WhitePawn;
9248 p = PieceToNumber((ChessSquare)p);
9249 if(p >= gameInfo.holdingsSize) p = 0;
9250 if(--board[p][BOARD_WIDTH-2] <= 0)
9251 board[p][BOARD_WIDTH-1] = EmptySquare;
9252 if((int)board[p][BOARD_WIDTH-2] < 0)
9253 board[p][BOARD_WIDTH-2] = 0;
9254 } else { /* black drop */
9255 p -= (int)BlackPawn;
9256 p = PieceToNumber((ChessSquare)p);
9257 if(p >= gameInfo.holdingsSize) p = 0;
9258 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9259 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9260 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9261 board[BOARD_HEIGHT-1-p][1] = 0;
9264 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9265 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9266 /* [HGM] holdings: Add to holdings, if holdings exist */
9267 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9268 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9269 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9272 if (p >= (int) BlackPawn) {
9273 p -= (int)BlackPawn;
9274 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9275 /* in Shogi restore piece to its original first */
9276 captured = (ChessSquare) (DEMOTED captured);
9279 p = PieceToNumber((ChessSquare)p);
9280 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9281 board[p][BOARD_WIDTH-2]++;
9282 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9284 p -= (int)WhitePawn;
9285 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9286 captured = (ChessSquare) (DEMOTED captured);
9289 p = PieceToNumber((ChessSquare)p);
9290 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9291 board[BOARD_HEIGHT-1-p][1]++;
9292 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9295 } else if (gameInfo.variant == VariantAtomic) {
9296 if (captured != EmptySquare) {
9298 for (y = toY-1; y <= toY+1; y++) {
9299 for (x = toX-1; x <= toX+1; x++) {
9300 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9301 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9302 board[y][x] = EmptySquare;
9306 board[toY][toX] = EmptySquare;
9309 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9310 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9312 if(promoChar == '+') {
9313 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9314 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9315 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9316 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9318 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9319 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9320 // [HGM] superchess: take promotion piece out of holdings
9321 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9322 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9323 if(!--board[k][BOARD_WIDTH-2])
9324 board[k][BOARD_WIDTH-1] = EmptySquare;
9326 if(!--board[BOARD_HEIGHT-1-k][1])
9327 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9333 /* Updates forwardMostMove */
9335 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9337 // forwardMostMove++; // [HGM] bare: moved downstream
9339 (void) CoordsToAlgebraic(boards[forwardMostMove],
9340 PosFlags(forwardMostMove),
9341 fromY, fromX, toY, toX, promoChar,
9342 parseList[forwardMostMove]);
9344 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9345 int timeLeft; static int lastLoadFlag=0; int king, piece;
9346 piece = boards[forwardMostMove][fromY][fromX];
9347 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9348 if(gameInfo.variant == VariantKnightmate)
9349 king += (int) WhiteUnicorn - (int) WhiteKing;
9350 if(forwardMostMove == 0) {
9351 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9352 fprintf(serverMoves, "%s;", UserName());
9353 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9354 fprintf(serverMoves, "%s;", second.tidy);
9355 fprintf(serverMoves, "%s;", first.tidy);
9356 if(gameMode == MachinePlaysWhite)
9357 fprintf(serverMoves, "%s;", UserName());
9358 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9359 fprintf(serverMoves, "%s;", second.tidy);
9360 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9361 lastLoadFlag = loadFlag;
9363 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9364 // print castling suffix
9365 if( toY == fromY && piece == king ) {
9367 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9369 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9372 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9373 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9374 boards[forwardMostMove][toY][toX] == EmptySquare
9375 && fromX != toX && fromY != toY)
9376 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9378 if(promoChar != NULLCHAR)
9379 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9381 char buf[MOVE_LEN*2], *p; int len;
9382 fprintf(serverMoves, "/%d/%d",
9383 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9384 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9385 else timeLeft = blackTimeRemaining/1000;
9386 fprintf(serverMoves, "/%d", timeLeft);
9387 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9388 if(p = strchr(buf, '=')) *p = NULLCHAR;
9389 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9390 fprintf(serverMoves, "/%s", buf);
9392 fflush(serverMoves);
9395 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9396 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9399 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9400 if (commentList[forwardMostMove+1] != NULL) {
9401 free(commentList[forwardMostMove+1]);
9402 commentList[forwardMostMove+1] = NULL;
9404 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9405 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9406 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9407 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9408 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9409 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9410 adjustedClock = FALSE;
9411 gameInfo.result = GameUnfinished;
9412 if (gameInfo.resultDetails != NULL) {
9413 free(gameInfo.resultDetails);
9414 gameInfo.resultDetails = NULL;
9416 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9417 moveList[forwardMostMove - 1]);
9418 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9424 if(gameInfo.variant != VariantShogi)
9425 strcat(parseList[forwardMostMove - 1], "+");
9429 strcat(parseList[forwardMostMove - 1], "#");
9432 if (appData.debugMode) {
9433 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9438 /* Updates currentMove if not pausing */
9440 ShowMove (int fromX, int fromY, int toX, int toY)
9442 int instant = (gameMode == PlayFromGameFile) ?
9443 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9444 if(appData.noGUI) return;
9445 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9447 if (forwardMostMove == currentMove + 1) {
9448 AnimateMove(boards[forwardMostMove - 1],
9449 fromX, fromY, toX, toY);
9451 if (appData.highlightLastMove) {
9452 SetHighlights(fromX, fromY, toX, toY);
9455 currentMove = forwardMostMove;
9458 if (instant) return;
9460 DisplayMove(currentMove - 1);
9461 DrawPosition(FALSE, boards[currentMove]);
9462 DisplayBothClocks();
9463 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9467 SendEgtPath (ChessProgramState *cps)
9468 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9469 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9471 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9474 char c, *q = name+1, *r, *s;
9476 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9477 while(*p && *p != ',') *q++ = *p++;
9479 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9480 strcmp(name, ",nalimov:") == 0 ) {
9481 // take nalimov path from the menu-changeable option first, if it is defined
9482 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9483 SendToProgram(buf,cps); // send egtbpath command for nalimov
9485 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9486 (s = StrStr(appData.egtFormats, name)) != NULL) {
9487 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9488 s = r = StrStr(s, ":") + 1; // beginning of path info
9489 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9490 c = *r; *r = 0; // temporarily null-terminate path info
9491 *--q = 0; // strip of trailig ':' from name
9492 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9494 SendToProgram(buf,cps); // send egtbpath command for this format
9496 if(*p == ',') p++; // read away comma to position for next format name
9501 InitChessProgram (ChessProgramState *cps, int setup)
9502 /* setup needed to setup FRC opening position */
9504 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9505 if (appData.noChessProgram) return;
9506 hintRequested = FALSE;
9507 bookRequested = FALSE;
9509 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9510 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9511 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9512 if(cps->memSize) { /* [HGM] memory */
9513 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9514 SendToProgram(buf, cps);
9516 SendEgtPath(cps); /* [HGM] EGT */
9517 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9518 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9519 SendToProgram(buf, cps);
9522 SendToProgram(cps->initString, cps);
9523 if (gameInfo.variant != VariantNormal &&
9524 gameInfo.variant != VariantLoadable
9525 /* [HGM] also send variant if board size non-standard */
9526 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9528 char *v = VariantName(gameInfo.variant);
9529 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9530 /* [HGM] in protocol 1 we have to assume all variants valid */
9531 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9532 DisplayFatalError(buf, 0, 1);
9536 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9537 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9538 if( gameInfo.variant == VariantXiangqi )
9539 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9540 if( gameInfo.variant == VariantShogi )
9541 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9542 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9543 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9544 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9545 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9546 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547 if( gameInfo.variant == VariantCourier )
9548 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9549 if( gameInfo.variant == VariantSuper )
9550 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9551 if( gameInfo.variant == VariantGreat )
9552 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9553 if( gameInfo.variant == VariantSChess )
9554 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9555 if( gameInfo.variant == VariantGrand )
9556 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9559 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9560 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9561 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9562 if(StrStr(cps->variants, b) == NULL) {
9563 // specific sized variant not known, check if general sizing allowed
9564 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9565 if(StrStr(cps->variants, "boardsize") == NULL) {
9566 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9567 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9568 DisplayFatalError(buf, 0, 1);
9571 /* [HGM] here we really should compare with the maximum supported board size */
9574 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9575 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9576 SendToProgram(buf, cps);
9578 currentlyInitializedVariant = gameInfo.variant;
9580 /* [HGM] send opening position in FRC to first engine */
9582 SendToProgram("force\n", cps);
9584 /* engine is now in force mode! Set flag to wake it up after first move. */
9585 setboardSpoiledMachineBlack = 1;
9589 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9590 SendToProgram(buf, cps);
9592 cps->maybeThinking = FALSE;
9593 cps->offeredDraw = 0;
9594 if (!appData.icsActive) {
9595 SendTimeControl(cps, movesPerSession, timeControl,
9596 timeIncrement, appData.searchDepth,
9599 if (appData.showThinking
9600 // [HGM] thinking: four options require thinking output to be sent
9601 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9603 SendToProgram("post\n", cps);
9605 SendToProgram("hard\n", cps);
9606 if (!appData.ponderNextMove) {
9607 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9608 it without being sure what state we are in first. "hard"
9609 is not a toggle, so that one is OK.
9611 SendToProgram("easy\n", cps);
9614 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9615 SendToProgram(buf, cps);
9617 cps->initDone = TRUE;
9618 ClearEngineOutputPane(cps == &second);
9623 StartChessProgram (ChessProgramState *cps)
9628 if (appData.noChessProgram) return;
9629 cps->initDone = FALSE;
9631 if (strcmp(cps->host, "localhost") == 0) {
9632 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9633 } else if (*appData.remoteShell == NULLCHAR) {
9634 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9636 if (*appData.remoteUser == NULLCHAR) {
9637 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9640 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9641 cps->host, appData.remoteUser, cps->program);
9643 err = StartChildProcess(buf, "", &cps->pr);
9647 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9648 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9649 if(cps != &first) return;
9650 appData.noChessProgram = TRUE;
9653 // DisplayFatalError(buf, err, 1);
9654 // cps->pr = NoProc;
9659 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9660 if (cps->protocolVersion > 1) {
9661 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9662 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9663 cps->comboCnt = 0; // and values of combo boxes
9664 SendToProgram(buf, cps);
9666 SendToProgram("xboard\n", cps);
9671 TwoMachinesEventIfReady P((void))
9673 static int curMess = 0;
9674 if (first.lastPing != first.lastPong) {
9675 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9676 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9679 if (second.lastPing != second.lastPong) {
9680 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9681 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9684 DisplayMessage("", ""); curMess = 0;
9690 MakeName (char *template)
9694 static char buf[MSG_SIZ];
9698 clock = time((time_t *)NULL);
9699 tm = localtime(&clock);
9701 while(*p++ = *template++) if(p[-1] == '%') {
9702 switch(*template++) {
9703 case 0: *p = 0; return buf;
9704 case 'Y': i = tm->tm_year+1900; break;
9705 case 'y': i = tm->tm_year-100; break;
9706 case 'M': i = tm->tm_mon+1; break;
9707 case 'd': i = tm->tm_mday; break;
9708 case 'h': i = tm->tm_hour; break;
9709 case 'm': i = tm->tm_min; break;
9710 case 's': i = tm->tm_sec; break;
9713 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9719 CountPlayers (char *p)
9722 while(p = strchr(p, '\n')) p++, n++; // count participants
9727 WriteTourneyFile (char *results, FILE *f)
9728 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9729 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9730 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9731 // create a file with tournament description
9732 fprintf(f, "-participants {%s}\n", appData.participants);
9733 fprintf(f, "-seedBase %d\n", appData.seedBase);
9734 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9735 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9736 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9737 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9738 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9739 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9740 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9741 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9742 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9743 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9744 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9745 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9747 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9749 fprintf(f, "-mps %d\n", appData.movesPerSession);
9750 fprintf(f, "-tc %s\n", appData.timeControl);
9751 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9753 fprintf(f, "-results \"%s\"\n", results);
9758 #define MAXENGINES 1000
9759 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9762 Substitute (char *participants, int expunge)
9764 int i, changed, changes=0, nPlayers=0;
9765 char *p, *q, *r, buf[MSG_SIZ];
9766 if(participants == NULL) return;
9767 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9768 r = p = participants; q = appData.participants;
9769 while(*p && *p == *q) {
9770 if(*p == '\n') r = p+1, nPlayers++;
9773 if(*p) { // difference
9774 while(*p && *p++ != '\n');
9775 while(*q && *q++ != '\n');
9777 changes = 1 + (strcmp(p, q) != 0);
9779 if(changes == 1) { // a single engine mnemonic was changed
9780 q = r; while(*q) nPlayers += (*q++ == '\n');
9781 p = buf; while(*r && (*p = *r++) != '\n') p++;
9783 NamesToList(firstChessProgramNames, command, mnemonic);
9784 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9785 if(mnemonic[i]) { // The substitute is valid
9787 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9788 flock(fileno(f), LOCK_EX);
9789 ParseArgsFromFile(f);
9790 fseek(f, 0, SEEK_SET);
9791 FREE(appData.participants); appData.participants = participants;
9792 if(expunge) { // erase results of replaced engine
9793 int len = strlen(appData.results), w, b, dummy;
9794 for(i=0; i<len; i++) {
9795 Pairing(i, nPlayers, &w, &b, &dummy);
9796 if((w == changed || b == changed) && appData.results[i] == '*') {
9797 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9802 for(i=0; i<len; i++) {
9803 Pairing(i, nPlayers, &w, &b, &dummy);
9804 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9807 WriteTourneyFile(appData.results, f);
9808 fclose(f); // release lock
9811 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9813 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9814 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9820 CreateTourney (char *name)
9823 if(matchMode && strcmp(name, appData.tourneyFile)) {
9824 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9826 if(name[0] == NULLCHAR) {
9827 if(appData.participants[0])
9828 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9831 f = fopen(name, "r");
9832 if(f) { // file exists
9833 ASSIGN(appData.tourneyFile, name);
9834 ParseArgsFromFile(f); // parse it
9836 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9837 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9838 DisplayError(_("Not enough participants"), 0);
9841 ASSIGN(appData.tourneyFile, name);
9842 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9843 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9846 appData.noChessProgram = FALSE;
9847 appData.clockMode = TRUE;
9853 NamesToList (char *names, char **engineList, char **engineMnemonic)
9855 char buf[MSG_SIZ], *p, *q;
9859 while(*p && *p != '\n') *q++ = *p++;
9861 if(engineList[i]) free(engineList[i]);
9862 engineList[i] = strdup(buf);
9864 TidyProgramName(engineList[i], "localhost", buf);
9865 if(engineMnemonic[i]) free(engineMnemonic[i]);
9866 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9868 sscanf(q + 8, "%s", buf + strlen(buf));
9871 engineMnemonic[i] = strdup(buf);
9873 if(i > MAXENGINES - 2) break;
9875 engineList[i] = engineMnemonic[i] = NULL;
9878 // following implemented as macro to avoid type limitations
9879 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9883 { // swap settings for first engine and other engine (so far only some selected options)
9888 SWAP(chessProgram, p)
9890 SWAP(hasOwnBookUCI, h)
9891 SWAP(protocolVersion, h)
9893 SWAP(scoreIsAbsolute, h)
9902 SetPlayer (int player)
9903 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9905 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9906 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9907 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9908 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9910 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9911 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9912 appData.firstHasOwnBookUCI = !appData.defNoBook;
9913 ParseArgsFromString(buf);
9919 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9920 { // determine players from game number
9921 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9923 if(appData.tourneyType == 0) {
9924 roundsPerCycle = (nPlayers - 1) | 1;
9925 pairingsPerRound = nPlayers / 2;
9926 } else if(appData.tourneyType > 0) {
9927 roundsPerCycle = nPlayers - appData.tourneyType;
9928 pairingsPerRound = appData.tourneyType;
9930 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9931 gamesPerCycle = gamesPerRound * roundsPerCycle;
9932 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9933 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9934 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9935 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9936 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9937 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9939 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9940 if(appData.roundSync) *syncInterval = gamesPerRound;
9942 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9944 if(appData.tourneyType == 0) {
9945 if(curPairing == (nPlayers-1)/2 ) {
9946 *whitePlayer = curRound;
9947 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9949 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9950 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9951 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9952 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9954 } else if(appData.tourneyType > 0) {
9955 *whitePlayer = curPairing;
9956 *blackPlayer = curRound + appData.tourneyType;
9959 // take care of white/black alternation per round.
9960 // For cycles and games this is already taken care of by default, derived from matchGame!
9961 return curRound & 1;
9965 NextTourneyGame (int nr, int *swapColors)
9966 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9968 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9970 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9971 tf = fopen(appData.tourneyFile, "r");
9972 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9973 ParseArgsFromFile(tf); fclose(tf);
9974 InitTimeControls(); // TC might be altered from tourney file
9976 nPlayers = CountPlayers(appData.participants); // count participants
9977 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9978 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9981 p = q = appData.results;
9982 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9983 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9984 DisplayMessage(_("Waiting for other game(s)"),"");
9985 waitingForGame = TRUE;
9986 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9989 waitingForGame = FALSE;
9992 if(appData.tourneyType < 0) {
9993 if(nr>=0 && !pairingReceived) {
9995 if(pairing.pr == NoProc) {
9996 if(!appData.pairingEngine[0]) {
9997 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10000 StartChessProgram(&pairing); // starts the pairing engine
10002 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10003 SendToProgram(buf, &pairing);
10004 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10005 SendToProgram(buf, &pairing);
10006 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10008 pairingReceived = 0; // ... so we continue here
10010 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10011 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10012 matchGame = 1; roundNr = nr / syncInterval + 1;
10015 if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10017 // redefine engines, engine dir, etc.
10018 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10019 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10021 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10022 SwapEngines(1); // and make that valid for second engine by swapping
10023 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10024 InitEngine(&second, 1);
10025 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10026 UpdateLogos(FALSE); // leave display to ModeHiglight()
10032 { // performs game initialization that does not invoke engines, and then tries to start the game
10033 int res, firstWhite, swapColors = 0;
10034 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10035 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10036 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10037 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10038 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10039 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10040 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10041 Reset(FALSE, first.pr != NoProc);
10042 res = LoadGameOrPosition(matchGame); // setup game
10043 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10044 if(!res) return; // abort when bad game/pos file
10045 TwoMachinesEvent();
10049 UserAdjudicationEvent (int result)
10051 ChessMove gameResult = GameIsDrawn;
10054 gameResult = WhiteWins;
10056 else if( result < 0 ) {
10057 gameResult = BlackWins;
10060 if( gameMode == TwoMachinesPlay ) {
10061 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10066 // [HGM] save: calculate checksum of game to make games easily identifiable
10068 StringCheckSum (char *s)
10071 if(s==NULL) return 0;
10072 while(*s) i = i*259 + *s++;
10080 for(i=backwardMostMove; i<forwardMostMove; i++) {
10081 sum += pvInfoList[i].depth;
10082 sum += StringCheckSum(parseList[i]);
10083 sum += StringCheckSum(commentList[i]);
10086 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10087 return sum + StringCheckSum(commentList[i]);
10088 } // end of save patch
10091 GameEnds (ChessMove result, char *resultDetails, int whosays)
10093 GameMode nextGameMode;
10095 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10097 if(endingGame) return; /* [HGM] crash: forbid recursion */
10099 if(twoBoards) { // [HGM] dual: switch back to one board
10100 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10101 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10103 if (appData.debugMode) {
10104 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10105 result, resultDetails ? resultDetails : "(null)", whosays);
10108 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10110 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10111 /* If we are playing on ICS, the server decides when the
10112 game is over, but the engine can offer to draw, claim
10116 if (appData.zippyPlay && first.initDone) {
10117 if (result == GameIsDrawn) {
10118 /* In case draw still needs to be claimed */
10119 SendToICS(ics_prefix);
10120 SendToICS("draw\n");
10121 } else if (StrCaseStr(resultDetails, "resign")) {
10122 SendToICS(ics_prefix);
10123 SendToICS("resign\n");
10127 endingGame = 0; /* [HGM] crash */
10131 /* If we're loading the game from a file, stop */
10132 if (whosays == GE_FILE) {
10133 (void) StopLoadGameTimer();
10137 /* Cancel draw offers */
10138 first.offeredDraw = second.offeredDraw = 0;
10140 /* If this is an ICS game, only ICS can really say it's done;
10141 if not, anyone can. */
10142 isIcsGame = (gameMode == IcsPlayingWhite ||
10143 gameMode == IcsPlayingBlack ||
10144 gameMode == IcsObserving ||
10145 gameMode == IcsExamining);
10147 if (!isIcsGame || whosays == GE_ICS) {
10148 /* OK -- not an ICS game, or ICS said it was done */
10150 if (!isIcsGame && !appData.noChessProgram)
10151 SetUserThinkingEnables();
10153 /* [HGM] if a machine claims the game end we verify this claim */
10154 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10155 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10157 ChessMove trueResult = (ChessMove) -1;
10159 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10160 first.twoMachinesColor[0] :
10161 second.twoMachinesColor[0] ;
10163 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10164 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10165 /* [HGM] verify: engine mate claims accepted if they were flagged */
10166 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10168 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10169 /* [HGM] verify: engine mate claims accepted if they were flagged */
10170 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10172 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10173 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10176 // now verify win claims, but not in drop games, as we don't understand those yet
10177 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10178 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10179 (result == WhiteWins && claimer == 'w' ||
10180 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10181 if (appData.debugMode) {
10182 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10183 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10185 if(result != trueResult) {
10186 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10187 result = claimer == 'w' ? BlackWins : WhiteWins;
10188 resultDetails = buf;
10191 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10192 && (forwardMostMove <= backwardMostMove ||
10193 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10194 (claimer=='b')==(forwardMostMove&1))
10196 /* [HGM] verify: draws that were not flagged are false claims */
10197 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10198 result = claimer == 'w' ? BlackWins : WhiteWins;
10199 resultDetails = buf;
10201 /* (Claiming a loss is accepted no questions asked!) */
10203 /* [HGM] bare: don't allow bare King to win */
10204 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10205 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10206 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10207 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10208 && result != GameIsDrawn)
10209 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10210 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10211 int p = (signed char)boards[forwardMostMove][i][j] - color;
10212 if(p >= 0 && p <= (int)WhiteKing) k++;
10214 if (appData.debugMode) {
10215 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10216 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10219 result = GameIsDrawn;
10220 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10221 resultDetails = buf;
10227 if(serverMoves != NULL && !loadFlag) { char c = '=';
10228 if(result==WhiteWins) c = '+';
10229 if(result==BlackWins) c = '-';
10230 if(resultDetails != NULL)
10231 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10233 if (resultDetails != NULL) {
10234 gameInfo.result = result;
10235 gameInfo.resultDetails = StrSave(resultDetails);
10237 /* display last move only if game was not loaded from file */
10238 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10239 DisplayMove(currentMove - 1);
10241 if (forwardMostMove != 0) {
10242 if (gameMode != PlayFromGameFile && gameMode != EditGame
10243 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10245 if (*appData.saveGameFile != NULLCHAR) {
10246 SaveGameToFile(appData.saveGameFile, TRUE);
10247 } else if (appData.autoSaveGames) {
10250 if (*appData.savePositionFile != NULLCHAR) {
10251 SavePositionToFile(appData.savePositionFile);
10256 /* Tell program how game ended in case it is learning */
10257 /* [HGM] Moved this to after saving the PGN, just in case */
10258 /* engine died and we got here through time loss. In that */
10259 /* case we will get a fatal error writing the pipe, which */
10260 /* would otherwise lose us the PGN. */
10261 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10262 /* output during GameEnds should never be fatal anymore */
10263 if (gameMode == MachinePlaysWhite ||
10264 gameMode == MachinePlaysBlack ||
10265 gameMode == TwoMachinesPlay ||
10266 gameMode == IcsPlayingWhite ||
10267 gameMode == IcsPlayingBlack ||
10268 gameMode == BeginningOfGame) {
10270 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10272 if (first.pr != NoProc) {
10273 SendToProgram(buf, &first);
10275 if (second.pr != NoProc &&
10276 gameMode == TwoMachinesPlay) {
10277 SendToProgram(buf, &second);
10282 if (appData.icsActive) {
10283 if (appData.quietPlay &&
10284 (gameMode == IcsPlayingWhite ||
10285 gameMode == IcsPlayingBlack)) {
10286 SendToICS(ics_prefix);
10287 SendToICS("set shout 1\n");
10289 nextGameMode = IcsIdle;
10290 ics_user_moved = FALSE;
10291 /* clean up premove. It's ugly when the game has ended and the
10292 * premove highlights are still on the board.
10295 gotPremove = FALSE;
10296 ClearPremoveHighlights();
10297 DrawPosition(FALSE, boards[currentMove]);
10299 if (whosays == GE_ICS) {
10302 if (gameMode == IcsPlayingWhite)
10304 else if(gameMode == IcsPlayingBlack)
10305 PlayIcsLossSound();
10308 if (gameMode == IcsPlayingBlack)
10310 else if(gameMode == IcsPlayingWhite)
10311 PlayIcsLossSound();
10314 PlayIcsDrawSound();
10317 PlayIcsUnfinishedSound();
10320 } else if (gameMode == EditGame ||
10321 gameMode == PlayFromGameFile ||
10322 gameMode == AnalyzeMode ||
10323 gameMode == AnalyzeFile) {
10324 nextGameMode = gameMode;
10326 nextGameMode = EndOfGame;
10331 nextGameMode = gameMode;
10334 if (appData.noChessProgram) {
10335 gameMode = nextGameMode;
10337 endingGame = 0; /* [HGM] crash */
10342 /* Put first chess program into idle state */
10343 if (first.pr != NoProc &&
10344 (gameMode == MachinePlaysWhite ||
10345 gameMode == MachinePlaysBlack ||
10346 gameMode == TwoMachinesPlay ||
10347 gameMode == IcsPlayingWhite ||
10348 gameMode == IcsPlayingBlack ||
10349 gameMode == BeginningOfGame)) {
10350 SendToProgram("force\n", &first);
10351 if (first.usePing) {
10353 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10354 SendToProgram(buf, &first);
10357 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10358 /* Kill off first chess program */
10359 if (first.isr != NULL)
10360 RemoveInputSource(first.isr);
10363 if (first.pr != NoProc) {
10365 DoSleep( appData.delayBeforeQuit );
10366 SendToProgram("quit\n", &first);
10367 DoSleep( appData.delayAfterQuit );
10368 DestroyChildProcess(first.pr, first.useSigterm);
10372 if (second.reuse) {
10373 /* Put second chess program into idle state */
10374 if (second.pr != NoProc &&
10375 gameMode == TwoMachinesPlay) {
10376 SendToProgram("force\n", &second);
10377 if (second.usePing) {
10379 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10380 SendToProgram(buf, &second);
10383 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10384 /* Kill off second chess program */
10385 if (second.isr != NULL)
10386 RemoveInputSource(second.isr);
10389 if (second.pr != NoProc) {
10390 DoSleep( appData.delayBeforeQuit );
10391 SendToProgram("quit\n", &second);
10392 DoSleep( appData.delayAfterQuit );
10393 DestroyChildProcess(second.pr, second.useSigterm);
10395 second.pr = NoProc;
10398 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10399 char resChar = '=';
10403 if (first.twoMachinesColor[0] == 'w') {
10406 second.matchWins++;
10411 if (first.twoMachinesColor[0] == 'b') {
10414 second.matchWins++;
10417 case GameUnfinished:
10423 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10424 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10425 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10426 ReserveGame(nextGame, resChar); // sets nextGame
10427 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10428 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10429 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10431 if (nextGame <= appData.matchGames && !abortMatch) {
10432 gameMode = nextGameMode;
10433 matchGame = nextGame; // this will be overruled in tourney mode!
10434 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10435 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10436 endingGame = 0; /* [HGM] crash */
10439 gameMode = nextGameMode;
10440 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10441 first.tidy, second.tidy,
10442 first.matchWins, second.matchWins,
10443 appData.matchGames - (first.matchWins + second.matchWins));
10444 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10445 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10446 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10447 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10448 first.twoMachinesColor = "black\n";
10449 second.twoMachinesColor = "white\n";
10451 first.twoMachinesColor = "white\n";
10452 second.twoMachinesColor = "black\n";
10456 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10457 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10459 gameMode = nextGameMode;
10461 endingGame = 0; /* [HGM] crash */
10462 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10463 if(matchMode == TRUE) { // match through command line: exit with or without popup
10465 ToNrEvent(forwardMostMove);
10466 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10468 } else DisplayFatalError(buf, 0, 0);
10469 } else { // match through menu; just stop, with or without popup
10470 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10473 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10474 } else DisplayNote(buf);
10476 if(ranking) free(ranking);
10480 /* Assumes program was just initialized (initString sent).
10481 Leaves program in force mode. */
10483 FeedMovesToProgram (ChessProgramState *cps, int upto)
10487 if (appData.debugMode)
10488 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10489 startedFromSetupPosition ? "position and " : "",
10490 backwardMostMove, upto, cps->which);
10491 if(currentlyInitializedVariant != gameInfo.variant) {
10493 // [HGM] variantswitch: make engine aware of new variant
10494 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10495 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10496 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10497 SendToProgram(buf, cps);
10498 currentlyInitializedVariant = gameInfo.variant;
10500 SendToProgram("force\n", cps);
10501 if (startedFromSetupPosition) {
10502 SendBoard(cps, backwardMostMove);
10503 if (appData.debugMode) {
10504 fprintf(debugFP, "feedMoves\n");
10507 for (i = backwardMostMove; i < upto; i++) {
10508 SendMoveToProgram(i, cps);
10514 ResurrectChessProgram ()
10516 /* The chess program may have exited.
10517 If so, restart it and feed it all the moves made so far. */
10518 static int doInit = 0;
10520 if (appData.noChessProgram) return 1;
10522 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10523 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10524 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10525 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10527 if (first.pr != NoProc) return 1;
10528 StartChessProgram(&first);
10530 InitChessProgram(&first, FALSE);
10531 FeedMovesToProgram(&first, currentMove);
10533 if (!first.sendTime) {
10534 /* can't tell gnuchess what its clock should read,
10535 so we bow to its notion. */
10537 timeRemaining[0][currentMove] = whiteTimeRemaining;
10538 timeRemaining[1][currentMove] = blackTimeRemaining;
10541 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10542 appData.icsEngineAnalyze) && first.analysisSupport) {
10543 SendToProgram("analyze\n", &first);
10544 first.analyzing = TRUE;
10550 * Button procedures
10553 Reset (int redraw, int init)
10557 if (appData.debugMode) {
10558 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10559 redraw, init, gameMode);
10561 CleanupTail(); // [HGM] vari: delete any stored variations
10562 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10563 pausing = pauseExamInvalid = FALSE;
10564 startedFromSetupPosition = blackPlaysFirst = FALSE;
10566 whiteFlag = blackFlag = FALSE;
10567 userOfferedDraw = FALSE;
10568 hintRequested = bookRequested = FALSE;
10569 first.maybeThinking = FALSE;
10570 second.maybeThinking = FALSE;
10571 first.bookSuspend = FALSE; // [HGM] book
10572 second.bookSuspend = FALSE;
10573 thinkOutput[0] = NULLCHAR;
10574 lastHint[0] = NULLCHAR;
10575 ClearGameInfo(&gameInfo);
10576 gameInfo.variant = StringToVariant(appData.variant);
10577 ics_user_moved = ics_clock_paused = FALSE;
10578 ics_getting_history = H_FALSE;
10580 white_holding[0] = black_holding[0] = NULLCHAR;
10581 ClearProgramStats();
10582 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10586 flipView = appData.flipView;
10587 ClearPremoveHighlights();
10588 gotPremove = FALSE;
10589 alarmSounded = FALSE;
10591 GameEnds(EndOfFile, NULL, GE_PLAYER);
10592 if(appData.serverMovesName != NULL) {
10593 /* [HGM] prepare to make moves file for broadcasting */
10594 clock_t t = clock();
10595 if(serverMoves != NULL) fclose(serverMoves);
10596 serverMoves = fopen(appData.serverMovesName, "r");
10597 if(serverMoves != NULL) {
10598 fclose(serverMoves);
10599 /* delay 15 sec before overwriting, so all clients can see end */
10600 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10602 serverMoves = fopen(appData.serverMovesName, "w");
10606 gameMode = BeginningOfGame;
10608 if(appData.icsActive) gameInfo.variant = VariantNormal;
10609 currentMove = forwardMostMove = backwardMostMove = 0;
10610 MarkTargetSquares(1);
10611 InitPosition(redraw);
10612 for (i = 0; i < MAX_MOVES; i++) {
10613 if (commentList[i] != NULL) {
10614 free(commentList[i]);
10615 commentList[i] = NULL;
10619 timeRemaining[0][0] = whiteTimeRemaining;
10620 timeRemaining[1][0] = blackTimeRemaining;
10622 if (first.pr == NoProc) {
10623 StartChessProgram(&first);
10626 InitChessProgram(&first, startedFromSetupPosition);
10629 DisplayMessage("", "");
10630 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10631 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10635 AutoPlayGameLoop ()
10638 if (!AutoPlayOneMove())
10640 if (matchMode || appData.timeDelay == 0)
10642 if (appData.timeDelay < 0)
10644 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10653 int fromX, fromY, toX, toY;
10655 if (appData.debugMode) {
10656 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10659 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10662 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10663 pvInfoList[currentMove].depth = programStats.depth;
10664 pvInfoList[currentMove].score = programStats.score;
10665 pvInfoList[currentMove].time = 0;
10666 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10669 if (currentMove >= forwardMostMove) {
10670 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10671 // gameMode = EndOfGame;
10672 // ModeHighlight();
10674 /* [AS] Clear current move marker at the end of a game */
10675 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10680 toX = moveList[currentMove][2] - AAA;
10681 toY = moveList[currentMove][3] - ONE;
10683 if (moveList[currentMove][1] == '@') {
10684 if (appData.highlightLastMove) {
10685 SetHighlights(-1, -1, toX, toY);
10688 fromX = moveList[currentMove][0] - AAA;
10689 fromY = moveList[currentMove][1] - ONE;
10691 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10693 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10695 if (appData.highlightLastMove) {
10696 SetHighlights(fromX, fromY, toX, toY);
10699 DisplayMove(currentMove);
10700 SendMoveToProgram(currentMove++, &first);
10701 DisplayBothClocks();
10702 DrawPosition(FALSE, boards[currentMove]);
10703 // [HGM] PV info: always display, routine tests if empty
10704 DisplayComment(currentMove - 1, commentList[currentMove]);
10710 LoadGameOneMove (ChessMove readAhead)
10712 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10713 char promoChar = NULLCHAR;
10714 ChessMove moveType;
10715 char move[MSG_SIZ];
10718 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10719 gameMode != AnalyzeMode && gameMode != Training) {
10724 yyboardindex = forwardMostMove;
10725 if (readAhead != EndOfFile) {
10726 moveType = readAhead;
10728 if (gameFileFP == NULL)
10730 moveType = (ChessMove) Myylex();
10734 switch (moveType) {
10736 if (appData.debugMode)
10737 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10740 /* append the comment but don't display it */
10741 AppendComment(currentMove, p, FALSE);
10744 case WhiteCapturesEnPassant:
10745 case BlackCapturesEnPassant:
10746 case WhitePromotion:
10747 case BlackPromotion:
10748 case WhiteNonPromotion:
10749 case BlackNonPromotion:
10751 case WhiteKingSideCastle:
10752 case WhiteQueenSideCastle:
10753 case BlackKingSideCastle:
10754 case BlackQueenSideCastle:
10755 case WhiteKingSideCastleWild:
10756 case WhiteQueenSideCastleWild:
10757 case BlackKingSideCastleWild:
10758 case BlackQueenSideCastleWild:
10760 case WhiteHSideCastleFR:
10761 case WhiteASideCastleFR:
10762 case BlackHSideCastleFR:
10763 case BlackASideCastleFR:
10765 if (appData.debugMode)
10766 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10767 fromX = currentMoveString[0] - AAA;
10768 fromY = currentMoveString[1] - ONE;
10769 toX = currentMoveString[2] - AAA;
10770 toY = currentMoveString[3] - ONE;
10771 promoChar = currentMoveString[4];
10776 if (appData.debugMode)
10777 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10778 fromX = moveType == WhiteDrop ?
10779 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10780 (int) CharToPiece(ToLower(currentMoveString[0]));
10782 toX = currentMoveString[2] - AAA;
10783 toY = currentMoveString[3] - ONE;
10789 case GameUnfinished:
10790 if (appData.debugMode)
10791 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10792 p = strchr(yy_text, '{');
10793 if (p == NULL) p = strchr(yy_text, '(');
10796 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10798 q = strchr(p, *p == '{' ? '}' : ')');
10799 if (q != NULL) *q = NULLCHAR;
10802 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10803 GameEnds(moveType, p, GE_FILE);
10805 if (cmailMsgLoaded) {
10807 flipView = WhiteOnMove(currentMove);
10808 if (moveType == GameUnfinished) flipView = !flipView;
10809 if (appData.debugMode)
10810 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10815 if (appData.debugMode)
10816 fprintf(debugFP, "Parser hit end of file\n");
10817 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10823 if (WhiteOnMove(currentMove)) {
10824 GameEnds(BlackWins, "Black mates", GE_FILE);
10826 GameEnds(WhiteWins, "White mates", GE_FILE);
10830 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10836 case MoveNumberOne:
10837 if (lastLoadGameStart == GNUChessGame) {
10838 /* GNUChessGames have numbers, but they aren't move numbers */
10839 if (appData.debugMode)
10840 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10841 yy_text, (int) moveType);
10842 return LoadGameOneMove(EndOfFile); /* tail recursion */
10844 /* else fall thru */
10849 /* Reached start of next game in file */
10850 if (appData.debugMode)
10851 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10852 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10858 if (WhiteOnMove(currentMove)) {
10859 GameEnds(BlackWins, "Black mates", GE_FILE);
10861 GameEnds(WhiteWins, "White mates", GE_FILE);
10865 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10871 case PositionDiagram: /* should not happen; ignore */
10872 case ElapsedTime: /* ignore */
10873 case NAG: /* ignore */
10874 if (appData.debugMode)
10875 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10876 yy_text, (int) moveType);
10877 return LoadGameOneMove(EndOfFile); /* tail recursion */
10880 if (appData.testLegality) {
10881 if (appData.debugMode)
10882 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10883 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10884 (forwardMostMove / 2) + 1,
10885 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10886 DisplayError(move, 0);
10889 if (appData.debugMode)
10890 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10891 yy_text, currentMoveString);
10892 fromX = currentMoveString[0] - AAA;
10893 fromY = currentMoveString[1] - ONE;
10894 toX = currentMoveString[2] - AAA;
10895 toY = currentMoveString[3] - ONE;
10896 promoChar = currentMoveString[4];
10900 case AmbiguousMove:
10901 if (appData.debugMode)
10902 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10903 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10904 (forwardMostMove / 2) + 1,
10905 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10906 DisplayError(move, 0);
10911 case ImpossibleMove:
10912 if (appData.debugMode)
10913 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10914 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10915 (forwardMostMove / 2) + 1,
10916 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10917 DisplayError(move, 0);
10923 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10924 DrawPosition(FALSE, boards[currentMove]);
10925 DisplayBothClocks();
10926 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10927 DisplayComment(currentMove - 1, commentList[currentMove]);
10929 (void) StopLoadGameTimer();
10931 cmailOldMove = forwardMostMove;
10934 /* currentMoveString is set as a side-effect of yylex */
10936 thinkOutput[0] = NULLCHAR;
10937 MakeMove(fromX, fromY, toX, toY, promoChar);
10938 currentMove = forwardMostMove;
10943 /* Load the nth game from the given file */
10945 LoadGameFromFile (char *filename, int n, char *title, int useList)
10950 if (strcmp(filename, "-") == 0) {
10954 f = fopen(filename, "rb");
10956 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10957 DisplayError(buf, errno);
10961 if (fseek(f, 0, 0) == -1) {
10962 /* f is not seekable; probably a pipe */
10965 if (useList && n == 0) {
10966 int error = GameListBuild(f);
10968 DisplayError(_("Cannot build game list"), error);
10969 } else if (!ListEmpty(&gameList) &&
10970 ((ListGame *) gameList.tailPred)->number > 1) {
10971 GameListPopUp(f, title);
10978 return LoadGame(f, n, title, FALSE);
10983 MakeRegisteredMove ()
10985 int fromX, fromY, toX, toY;
10987 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10988 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10991 if (appData.debugMode)
10992 fprintf(debugFP, "Restoring %s for game %d\n",
10993 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10995 thinkOutput[0] = NULLCHAR;
10996 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10997 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10998 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10999 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11000 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11001 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11002 MakeMove(fromX, fromY, toX, toY, promoChar);
11003 ShowMove(fromX, fromY, toX, toY);
11005 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11012 if (WhiteOnMove(currentMove)) {
11013 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11015 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11020 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11027 if (WhiteOnMove(currentMove)) {
11028 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11030 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11035 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11046 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11048 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11052 if (gameNumber > nCmailGames) {
11053 DisplayError(_("No more games in this message"), 0);
11056 if (f == lastLoadGameFP) {
11057 int offset = gameNumber - lastLoadGameNumber;
11059 cmailMsg[0] = NULLCHAR;
11060 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11061 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11062 nCmailMovesRegistered--;
11064 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11065 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11066 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11069 if (! RegisterMove()) return FALSE;
11073 retVal = LoadGame(f, gameNumber, title, useList);
11075 /* Make move registered during previous look at this game, if any */
11076 MakeRegisteredMove();
11078 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11079 commentList[currentMove]
11080 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11081 DisplayComment(currentMove - 1, commentList[currentMove]);
11087 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11089 ReloadGame (int offset)
11091 int gameNumber = lastLoadGameNumber + offset;
11092 if (lastLoadGameFP == NULL) {
11093 DisplayError(_("No game has been loaded yet"), 0);
11096 if (gameNumber <= 0) {
11097 DisplayError(_("Can't back up any further"), 0);
11100 if (cmailMsgLoaded) {
11101 return CmailLoadGame(lastLoadGameFP, gameNumber,
11102 lastLoadGameTitle, lastLoadGameUseList);
11104 return LoadGame(lastLoadGameFP, gameNumber,
11105 lastLoadGameTitle, lastLoadGameUseList);
11109 int keys[EmptySquare+1];
11112 PositionMatches (Board b1, Board b2)
11115 switch(appData.searchMode) {
11116 case 1: return CompareWithRights(b1, b2);
11118 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11119 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11123 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11124 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11125 sum += keys[b1[r][f]] - keys[b2[r][f]];
11129 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11130 sum += keys[b1[r][f]] - keys[b2[r][f]];
11142 int pieceList[256], quickBoard[256];
11143 ChessSquare pieceType[256] = { EmptySquare };
11144 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11145 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11146 int soughtTotal, turn;
11147 Boolean epOK, flipSearch;
11150 unsigned char piece, to;
11153 #define DSIZE (250000)
11155 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11156 Move *moveDatabase = initialSpace;
11157 unsigned int movePtr, dataSize = DSIZE;
11160 MakePieceList (Board board, int *counts)
11162 int r, f, n=Q_PROMO, total=0;
11163 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11164 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11165 int sq = f + (r<<4);
11166 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11167 quickBoard[sq] = ++n;
11169 pieceType[n] = board[r][f];
11170 counts[board[r][f]]++;
11171 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11172 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11176 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11181 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11183 int sq = fromX + (fromY<<4);
11184 int piece = quickBoard[sq];
11185 quickBoard[sq] = 0;
11186 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11187 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11188 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11189 moveDatabase[movePtr++].piece = Q_WCASTL;
11190 quickBoard[sq] = piece;
11191 piece = quickBoard[from]; quickBoard[from] = 0;
11192 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11194 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11195 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11196 moveDatabase[movePtr++].piece = Q_BCASTL;
11197 quickBoard[sq] = piece;
11198 piece = quickBoard[from]; quickBoard[from] = 0;
11199 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11201 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11202 quickBoard[(fromY<<4)+toX] = 0;
11203 moveDatabase[movePtr].piece = Q_EP;
11204 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11205 moveDatabase[movePtr].to = sq;
11207 if(promoPiece != pieceType[piece]) {
11208 moveDatabase[movePtr++].piece = Q_PROMO;
11209 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11211 moveDatabase[movePtr].piece = piece;
11212 quickBoard[sq] = piece;
11217 PackGame (Board board)
11219 Move *newSpace = NULL;
11220 moveDatabase[movePtr].piece = 0; // terminate previous game
11221 if(movePtr > dataSize) {
11222 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11223 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11224 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11227 Move *p = moveDatabase, *q = newSpace;
11228 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11229 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11230 moveDatabase = newSpace;
11231 } else { // calloc failed, we must be out of memory. Too bad...
11232 dataSize = 0; // prevent calloc events for all subsequent games
11233 return 0; // and signal this one isn't cached
11237 MakePieceList(board, counts);
11242 QuickCompare (Board board, int *minCounts, int *maxCounts)
11243 { // compare according to search mode
11245 switch(appData.searchMode)
11247 case 1: // exact position match
11248 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11249 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11250 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11253 case 2: // can have extra material on empty squares
11254 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11255 if(board[r][f] == EmptySquare) continue;
11256 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11259 case 3: // material with exact Pawn structure
11260 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11261 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11262 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11263 } // fall through to material comparison
11264 case 4: // exact material
11265 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11267 case 6: // material range with given imbalance
11268 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11269 // fall through to range comparison
11270 case 5: // material range
11271 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11277 QuickScan (Board board, Move *move)
11278 { // reconstruct game,and compare all positions in it
11279 int cnt=0, stretch=0, total = MakePieceList(board, counts);
11281 int piece = move->piece;
11282 int to = move->to, from = pieceList[piece];
11283 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11284 if(!piece) return -1;
11285 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11286 piece = (++move)->piece;
11287 from = pieceList[piece];
11288 counts[pieceType[piece]]--;
11289 pieceType[piece] = (ChessSquare) move->to;
11290 counts[move->to]++;
11291 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11292 counts[pieceType[quickBoard[to]]]--;
11293 quickBoard[to] = 0; total--;
11296 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11297 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11298 from = pieceList[piece]; // so this must be King
11299 quickBoard[from] = 0;
11300 quickBoard[to] = piece;
11301 pieceList[piece] = to;
11306 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11307 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11308 quickBoard[from] = 0;
11309 quickBoard[to] = piece;
11310 pieceList[piece] = to;
11312 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11313 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11314 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11315 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11317 static int lastCounts[EmptySquare+1];
11319 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11320 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11321 } else stretch = 0;
11322 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11331 flipSearch = FALSE;
11332 CopyBoard(soughtBoard, boards[currentMove]);
11333 soughtTotal = MakePieceList(soughtBoard, maxSought);
11334 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11335 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11336 CopyBoard(reverseBoard, boards[currentMove]);
11337 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11338 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11339 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11340 reverseBoard[r][f] = piece;
11342 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11343 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11344 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11345 || (boards[currentMove][CASTLING][2] == NoRights ||
11346 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11347 && (boards[currentMove][CASTLING][5] == NoRights ||
11348 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11351 CopyBoard(flipBoard, soughtBoard);
11352 CopyBoard(rotateBoard, reverseBoard);
11353 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11354 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11355 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11358 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11359 if(appData.searchMode >= 5) {
11360 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11361 MakePieceList(soughtBoard, minSought);
11362 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11364 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11365 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11368 GameInfo dummyInfo;
11371 GameContainsPosition (FILE *f, ListGame *lg)
11373 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11374 int fromX, fromY, toX, toY;
11376 static int initDone=FALSE;
11378 // weed out games based on numerical tag comparison
11379 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11380 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11381 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11382 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11384 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11387 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11388 else CopyBoard(boards[scratch], initialPosition); // default start position
11391 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11392 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11395 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11396 fseek(f, lg->offset, 0);
11399 yyboardindex = scratch;
11400 quickFlag = plyNr+1;
11405 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11411 if(plyNr) return -1; // after we have seen moves, this is for new game
11414 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11415 case ImpossibleMove:
11416 case WhiteWins: // game ends here with these four
11419 case GameUnfinished:
11423 if(appData.testLegality) return -1;
11424 case WhiteCapturesEnPassant:
11425 case BlackCapturesEnPassant:
11426 case WhitePromotion:
11427 case BlackPromotion:
11428 case WhiteNonPromotion:
11429 case BlackNonPromotion:
11431 case WhiteKingSideCastle:
11432 case WhiteQueenSideCastle:
11433 case BlackKingSideCastle:
11434 case BlackQueenSideCastle:
11435 case WhiteKingSideCastleWild:
11436 case WhiteQueenSideCastleWild:
11437 case BlackKingSideCastleWild:
11438 case BlackQueenSideCastleWild:
11439 case WhiteHSideCastleFR:
11440 case WhiteASideCastleFR:
11441 case BlackHSideCastleFR:
11442 case BlackASideCastleFR:
11443 fromX = currentMoveString[0] - AAA;
11444 fromY = currentMoveString[1] - ONE;
11445 toX = currentMoveString[2] - AAA;
11446 toY = currentMoveString[3] - ONE;
11447 promoChar = currentMoveString[4];
11451 fromX = next == WhiteDrop ?
11452 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11453 (int) CharToPiece(ToLower(currentMoveString[0]));
11455 toX = currentMoveString[2] - AAA;
11456 toY = currentMoveString[3] - ONE;
11460 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11462 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11463 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11464 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11465 if(appData.findMirror) {
11466 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11467 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11472 /* Load the nth game from open file f */
11474 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11478 int gn = gameNumber;
11479 ListGame *lg = NULL;
11480 int numPGNTags = 0;
11482 GameMode oldGameMode;
11483 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11485 if (appData.debugMode)
11486 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11488 if (gameMode == Training )
11489 SetTrainingModeOff();
11491 oldGameMode = gameMode;
11492 if (gameMode != BeginningOfGame) {
11493 Reset(FALSE, TRUE);
11497 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11498 fclose(lastLoadGameFP);
11502 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11505 fseek(f, lg->offset, 0);
11506 GameListHighlight(gameNumber);
11507 pos = lg->position;
11511 DisplayError(_("Game number out of range"), 0);
11516 if (fseek(f, 0, 0) == -1) {
11517 if (f == lastLoadGameFP ?
11518 gameNumber == lastLoadGameNumber + 1 :
11522 DisplayError(_("Can't seek on game file"), 0);
11527 lastLoadGameFP = f;
11528 lastLoadGameNumber = gameNumber;
11529 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11530 lastLoadGameUseList = useList;
11534 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11535 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11536 lg->gameInfo.black);
11538 } else if (*title != NULLCHAR) {
11539 if (gameNumber > 1) {
11540 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11543 DisplayTitle(title);
11547 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11548 gameMode = PlayFromGameFile;
11552 currentMove = forwardMostMove = backwardMostMove = 0;
11553 CopyBoard(boards[0], initialPosition);
11557 * Skip the first gn-1 games in the file.
11558 * Also skip over anything that precedes an identifiable
11559 * start of game marker, to avoid being confused by
11560 * garbage at the start of the file. Currently
11561 * recognized start of game markers are the move number "1",
11562 * the pattern "gnuchess .* game", the pattern
11563 * "^[#;%] [^ ]* game file", and a PGN tag block.
11564 * A game that starts with one of the latter two patterns
11565 * will also have a move number 1, possibly
11566 * following a position diagram.
11567 * 5-4-02: Let's try being more lenient and allowing a game to
11568 * start with an unnumbered move. Does that break anything?
11570 cm = lastLoadGameStart = EndOfFile;
11572 yyboardindex = forwardMostMove;
11573 cm = (ChessMove) Myylex();
11576 if (cmailMsgLoaded) {
11577 nCmailGames = CMAIL_MAX_GAMES - gn;
11580 DisplayError(_("Game not found in file"), 0);
11587 lastLoadGameStart = cm;
11590 case MoveNumberOne:
11591 switch (lastLoadGameStart) {
11596 case MoveNumberOne:
11598 gn--; /* count this game */
11599 lastLoadGameStart = cm;
11608 switch (lastLoadGameStart) {
11611 case MoveNumberOne:
11613 gn--; /* count this game */
11614 lastLoadGameStart = cm;
11617 lastLoadGameStart = cm; /* game counted already */
11625 yyboardindex = forwardMostMove;
11626 cm = (ChessMove) Myylex();
11627 } while (cm == PGNTag || cm == Comment);
11634 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11635 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11636 != CMAIL_OLD_RESULT) {
11638 cmailResult[ CMAIL_MAX_GAMES
11639 - gn - 1] = CMAIL_OLD_RESULT;
11645 /* Only a NormalMove can be at the start of a game
11646 * without a position diagram. */
11647 if (lastLoadGameStart == EndOfFile ) {
11649 lastLoadGameStart = MoveNumberOne;
11658 if (appData.debugMode)
11659 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11661 if (cm == XBoardGame) {
11662 /* Skip any header junk before position diagram and/or move 1 */
11664 yyboardindex = forwardMostMove;
11665 cm = (ChessMove) Myylex();
11667 if (cm == EndOfFile ||
11668 cm == GNUChessGame || cm == XBoardGame) {
11669 /* Empty game; pretend end-of-file and handle later */
11674 if (cm == MoveNumberOne || cm == PositionDiagram ||
11675 cm == PGNTag || cm == Comment)
11678 } else if (cm == GNUChessGame) {
11679 if (gameInfo.event != NULL) {
11680 free(gameInfo.event);
11682 gameInfo.event = StrSave(yy_text);
11685 startedFromSetupPosition = FALSE;
11686 while (cm == PGNTag) {
11687 if (appData.debugMode)
11688 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11689 err = ParsePGNTag(yy_text, &gameInfo);
11690 if (!err) numPGNTags++;
11692 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11693 if(gameInfo.variant != oldVariant) {
11694 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11695 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11696 InitPosition(TRUE);
11697 oldVariant = gameInfo.variant;
11698 if (appData.debugMode)
11699 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11703 if (gameInfo.fen != NULL) {
11704 Board initial_position;
11705 startedFromSetupPosition = TRUE;
11706 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11708 DisplayError(_("Bad FEN position in file"), 0);
11711 CopyBoard(boards[0], initial_position);
11712 if (blackPlaysFirst) {
11713 currentMove = forwardMostMove = backwardMostMove = 1;
11714 CopyBoard(boards[1], initial_position);
11715 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11716 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11717 timeRemaining[0][1] = whiteTimeRemaining;
11718 timeRemaining[1][1] = blackTimeRemaining;
11719 if (commentList[0] != NULL) {
11720 commentList[1] = commentList[0];
11721 commentList[0] = NULL;
11724 currentMove = forwardMostMove = backwardMostMove = 0;
11726 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11728 initialRulePlies = FENrulePlies;
11729 for( i=0; i< nrCastlingRights; i++ )
11730 initialRights[i] = initial_position[CASTLING][i];
11732 yyboardindex = forwardMostMove;
11733 free(gameInfo.fen);
11734 gameInfo.fen = NULL;
11737 yyboardindex = forwardMostMove;
11738 cm = (ChessMove) Myylex();
11740 /* Handle comments interspersed among the tags */
11741 while (cm == Comment) {
11743 if (appData.debugMode)
11744 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11746 AppendComment(currentMove, p, FALSE);
11747 yyboardindex = forwardMostMove;
11748 cm = (ChessMove) Myylex();
11752 /* don't rely on existence of Event tag since if game was
11753 * pasted from clipboard the Event tag may not exist
11755 if (numPGNTags > 0){
11757 if (gameInfo.variant == VariantNormal) {
11758 VariantClass v = StringToVariant(gameInfo.event);
11759 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11760 if(v < VariantShogi) gameInfo.variant = v;
11763 if( appData.autoDisplayTags ) {
11764 tags = PGNTags(&gameInfo);
11765 TagsPopUp(tags, CmailMsg());
11770 /* Make something up, but don't display it now */
11775 if (cm == PositionDiagram) {
11778 Board initial_position;
11780 if (appData.debugMode)
11781 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11783 if (!startedFromSetupPosition) {
11785 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11786 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11797 initial_position[i][j++] = CharToPiece(*p);
11800 while (*p == ' ' || *p == '\t' ||
11801 *p == '\n' || *p == '\r') p++;
11803 if (strncmp(p, "black", strlen("black"))==0)
11804 blackPlaysFirst = TRUE;
11806 blackPlaysFirst = FALSE;
11807 startedFromSetupPosition = TRUE;
11809 CopyBoard(boards[0], initial_position);
11810 if (blackPlaysFirst) {
11811 currentMove = forwardMostMove = backwardMostMove = 1;
11812 CopyBoard(boards[1], initial_position);
11813 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11814 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11815 timeRemaining[0][1] = whiteTimeRemaining;
11816 timeRemaining[1][1] = blackTimeRemaining;
11817 if (commentList[0] != NULL) {
11818 commentList[1] = commentList[0];
11819 commentList[0] = NULL;
11822 currentMove = forwardMostMove = backwardMostMove = 0;
11825 yyboardindex = forwardMostMove;
11826 cm = (ChessMove) Myylex();
11829 if (first.pr == NoProc) {
11830 StartChessProgram(&first);
11832 InitChessProgram(&first, FALSE);
11833 SendToProgram("force\n", &first);
11834 if (startedFromSetupPosition) {
11835 SendBoard(&first, forwardMostMove);
11836 if (appData.debugMode) {
11837 fprintf(debugFP, "Load Game\n");
11839 DisplayBothClocks();
11842 /* [HGM] server: flag to write setup moves in broadcast file as one */
11843 loadFlag = appData.suppressLoadMoves;
11845 while (cm == Comment) {
11847 if (appData.debugMode)
11848 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11850 AppendComment(currentMove, p, FALSE);
11851 yyboardindex = forwardMostMove;
11852 cm = (ChessMove) Myylex();
11855 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11856 cm == WhiteWins || cm == BlackWins ||
11857 cm == GameIsDrawn || cm == GameUnfinished) {
11858 DisplayMessage("", _("No moves in game"));
11859 if (cmailMsgLoaded) {
11860 if (appData.debugMode)
11861 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11865 DrawPosition(FALSE, boards[currentMove]);
11866 DisplayBothClocks();
11867 gameMode = EditGame;
11874 // [HGM] PV info: routine tests if comment empty
11875 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11876 DisplayComment(currentMove - 1, commentList[currentMove]);
11878 if (!matchMode && appData.timeDelay != 0)
11879 DrawPosition(FALSE, boards[currentMove]);
11881 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11882 programStats.ok_to_send = 1;
11885 /* if the first token after the PGN tags is a move
11886 * and not move number 1, retrieve it from the parser
11888 if (cm != MoveNumberOne)
11889 LoadGameOneMove(cm);
11891 /* load the remaining moves from the file */
11892 while (LoadGameOneMove(EndOfFile)) {
11893 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11894 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11897 /* rewind to the start of the game */
11898 currentMove = backwardMostMove;
11900 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11902 if (oldGameMode == AnalyzeFile ||
11903 oldGameMode == AnalyzeMode) {
11904 AnalyzeFileEvent();
11907 if (!matchMode && pos >= 0) {
11908 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11910 if (matchMode || appData.timeDelay == 0) {
11912 } else if (appData.timeDelay > 0) {
11913 AutoPlayGameLoop();
11916 if (appData.debugMode)
11917 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11919 loadFlag = 0; /* [HGM] true game starts */
11923 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11925 ReloadPosition (int offset)
11927 int positionNumber = lastLoadPositionNumber + offset;
11928 if (lastLoadPositionFP == NULL) {
11929 DisplayError(_("No position has been loaded yet"), 0);
11932 if (positionNumber <= 0) {
11933 DisplayError(_("Can't back up any further"), 0);
11936 return LoadPosition(lastLoadPositionFP, positionNumber,
11937 lastLoadPositionTitle);
11940 /* Load the nth position from the given file */
11942 LoadPositionFromFile (char *filename, int n, char *title)
11947 if (strcmp(filename, "-") == 0) {
11948 return LoadPosition(stdin, n, "stdin");
11950 f = fopen(filename, "rb");
11952 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11953 DisplayError(buf, errno);
11956 return LoadPosition(f, n, title);
11961 /* Load the nth position from the given open file, and close it */
11963 LoadPosition (FILE *f, int positionNumber, char *title)
11965 char *p, line[MSG_SIZ];
11966 Board initial_position;
11967 int i, j, fenMode, pn;
11969 if (gameMode == Training )
11970 SetTrainingModeOff();
11972 if (gameMode != BeginningOfGame) {
11973 Reset(FALSE, TRUE);
11975 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11976 fclose(lastLoadPositionFP);
11978 if (positionNumber == 0) positionNumber = 1;
11979 lastLoadPositionFP = f;
11980 lastLoadPositionNumber = positionNumber;
11981 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11982 if (first.pr == NoProc && !appData.noChessProgram) {
11983 StartChessProgram(&first);
11984 InitChessProgram(&first, FALSE);
11986 pn = positionNumber;
11987 if (positionNumber < 0) {
11988 /* Negative position number means to seek to that byte offset */
11989 if (fseek(f, -positionNumber, 0) == -1) {
11990 DisplayError(_("Can't seek on position file"), 0);
11995 if (fseek(f, 0, 0) == -1) {
11996 if (f == lastLoadPositionFP ?
11997 positionNumber == lastLoadPositionNumber + 1 :
11998 positionNumber == 1) {
12001 DisplayError(_("Can't seek on position file"), 0);
12006 /* See if this file is FEN or old-style xboard */
12007 if (fgets(line, MSG_SIZ, f) == NULL) {
12008 DisplayError(_("Position not found in file"), 0);
12011 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12012 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12015 if (fenMode || line[0] == '#') pn--;
12017 /* skip positions before number pn */
12018 if (fgets(line, MSG_SIZ, f) == NULL) {
12020 DisplayError(_("Position not found in file"), 0);
12023 if (fenMode || line[0] == '#') pn--;
12028 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12029 DisplayError(_("Bad FEN position in file"), 0);
12033 (void) fgets(line, MSG_SIZ, f);
12034 (void) fgets(line, MSG_SIZ, f);
12036 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12037 (void) fgets(line, MSG_SIZ, f);
12038 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12041 initial_position[i][j++] = CharToPiece(*p);
12045 blackPlaysFirst = FALSE;
12047 (void) fgets(line, MSG_SIZ, f);
12048 if (strncmp(line, "black", strlen("black"))==0)
12049 blackPlaysFirst = TRUE;
12052 startedFromSetupPosition = TRUE;
12054 CopyBoard(boards[0], initial_position);
12055 if (blackPlaysFirst) {
12056 currentMove = forwardMostMove = backwardMostMove = 1;
12057 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12058 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12059 CopyBoard(boards[1], initial_position);
12060 DisplayMessage("", _("Black to play"));
12062 currentMove = forwardMostMove = backwardMostMove = 0;
12063 DisplayMessage("", _("White to play"));
12065 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12066 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12067 SendToProgram("force\n", &first);
12068 SendBoard(&first, forwardMostMove);
12070 if (appData.debugMode) {
12072 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12073 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12074 fprintf(debugFP, "Load Position\n");
12077 if (positionNumber > 1) {
12078 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12079 DisplayTitle(line);
12081 DisplayTitle(title);
12083 gameMode = EditGame;
12086 timeRemaining[0][1] = whiteTimeRemaining;
12087 timeRemaining[1][1] = blackTimeRemaining;
12088 DrawPosition(FALSE, boards[currentMove]);
12095 CopyPlayerNameIntoFileName (char **dest, char *src)
12097 while (*src != NULLCHAR && *src != ',') {
12102 *(*dest)++ = *src++;
12108 DefaultFileName (char *ext)
12110 static char def[MSG_SIZ];
12113 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12115 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12117 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12119 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12126 /* Save the current game to the given file */
12128 SaveGameToFile (char *filename, int append)
12132 int result, i, t,tot=0;
12134 if (strcmp(filename, "-") == 0) {
12135 return SaveGame(stdout, 0, NULL);
12137 for(i=0; i<10; i++) { // upto 10 tries
12138 f = fopen(filename, append ? "a" : "w");
12139 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12140 if(f || errno != 13) break;
12141 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12145 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12146 DisplayError(buf, errno);
12149 safeStrCpy(buf, lastMsg, MSG_SIZ);
12150 DisplayMessage(_("Waiting for access to save file"), "");
12151 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12152 DisplayMessage(_("Saving game"), "");
12153 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12154 result = SaveGame(f, 0, NULL);
12155 DisplayMessage(buf, "");
12162 SavePart (char *str)
12164 static char buf[MSG_SIZ];
12167 p = strchr(str, ' ');
12168 if (p == NULL) return str;
12169 strncpy(buf, str, p - str);
12170 buf[p - str] = NULLCHAR;
12174 #define PGN_MAX_LINE 75
12176 #define PGN_SIDE_WHITE 0
12177 #define PGN_SIDE_BLACK 1
12180 FindFirstMoveOutOfBook (int side)
12184 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12185 int index = backwardMostMove;
12186 int has_book_hit = 0;
12188 if( (index % 2) != side ) {
12192 while( index < forwardMostMove ) {
12193 /* Check to see if engine is in book */
12194 int depth = pvInfoList[index].depth;
12195 int score = pvInfoList[index].score;
12201 else if( score == 0 && depth == 63 ) {
12202 in_book = 1; /* Zappa */
12204 else if( score == 2 && depth == 99 ) {
12205 in_book = 1; /* Abrok */
12208 has_book_hit += in_book;
12224 GetOutOfBookInfo (char * buf)
12228 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12230 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12231 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12235 if( oob[0] >= 0 || oob[1] >= 0 ) {
12236 for( i=0; i<2; i++ ) {
12240 if( i > 0 && oob[0] >= 0 ) {
12241 strcat( buf, " " );
12244 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12245 sprintf( buf+strlen(buf), "%s%.2f",
12246 pvInfoList[idx].score >= 0 ? "+" : "",
12247 pvInfoList[idx].score / 100.0 );
12253 /* Save game in PGN style and close the file */
12255 SaveGamePGN (FILE *f)
12257 int i, offset, linelen, newblock;
12261 int movelen, numlen, blank;
12262 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12264 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12266 tm = time((time_t *) NULL);
12268 PrintPGNTags(f, &gameInfo);
12270 if (backwardMostMove > 0 || startedFromSetupPosition) {
12271 char *fen = PositionToFEN(backwardMostMove, NULL);
12272 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12273 fprintf(f, "\n{--------------\n");
12274 PrintPosition(f, backwardMostMove);
12275 fprintf(f, "--------------}\n");
12279 /* [AS] Out of book annotation */
12280 if( appData.saveOutOfBookInfo ) {
12283 GetOutOfBookInfo( buf );
12285 if( buf[0] != '\0' ) {
12286 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12293 i = backwardMostMove;
12297 while (i < forwardMostMove) {
12298 /* Print comments preceding this move */
12299 if (commentList[i] != NULL) {
12300 if (linelen > 0) fprintf(f, "\n");
12301 fprintf(f, "%s", commentList[i]);
12306 /* Format move number */
12308 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12311 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12313 numtext[0] = NULLCHAR;
12315 numlen = strlen(numtext);
12318 /* Print move number */
12319 blank = linelen > 0 && numlen > 0;
12320 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12329 fprintf(f, "%s", numtext);
12333 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12334 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12337 blank = linelen > 0 && movelen > 0;
12338 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12347 fprintf(f, "%s", move_buffer);
12348 linelen += movelen;
12350 /* [AS] Add PV info if present */
12351 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12352 /* [HGM] add time */
12353 char buf[MSG_SIZ]; int seconds;
12355 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12361 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12364 seconds = (seconds + 4)/10; // round to full seconds
12366 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12368 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12371 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12372 pvInfoList[i].score >= 0 ? "+" : "",
12373 pvInfoList[i].score / 100.0,
12374 pvInfoList[i].depth,
12377 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12379 /* Print score/depth */
12380 blank = linelen > 0 && movelen > 0;
12381 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12390 fprintf(f, "%s", move_buffer);
12391 linelen += movelen;
12397 /* Start a new line */
12398 if (linelen > 0) fprintf(f, "\n");
12400 /* Print comments after last move */
12401 if (commentList[i] != NULL) {
12402 fprintf(f, "%s\n", commentList[i]);
12406 if (gameInfo.resultDetails != NULL &&
12407 gameInfo.resultDetails[0] != NULLCHAR) {
12408 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12409 PGNResult(gameInfo.result));
12411 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12415 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12419 /* Save game in old style and close the file */
12421 SaveGameOldStyle (FILE *f)
12426 tm = time((time_t *) NULL);
12428 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12431 if (backwardMostMove > 0 || startedFromSetupPosition) {
12432 fprintf(f, "\n[--------------\n");
12433 PrintPosition(f, backwardMostMove);
12434 fprintf(f, "--------------]\n");
12439 i = backwardMostMove;
12440 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12442 while (i < forwardMostMove) {
12443 if (commentList[i] != NULL) {
12444 fprintf(f, "[%s]\n", commentList[i]);
12447 if ((i % 2) == 1) {
12448 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12451 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12453 if (commentList[i] != NULL) {
12457 if (i >= forwardMostMove) {
12461 fprintf(f, "%s\n", parseList[i]);
12466 if (commentList[i] != NULL) {
12467 fprintf(f, "[%s]\n", commentList[i]);
12470 /* This isn't really the old style, but it's close enough */
12471 if (gameInfo.resultDetails != NULL &&
12472 gameInfo.resultDetails[0] != NULLCHAR) {
12473 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12474 gameInfo.resultDetails);
12476 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12483 /* Save the current game to open file f and close the file */
12485 SaveGame (FILE *f, int dummy, char *dummy2)
12487 if (gameMode == EditPosition) EditPositionDone(TRUE);
12488 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12489 if (appData.oldSaveStyle)
12490 return SaveGameOldStyle(f);
12492 return SaveGamePGN(f);
12495 /* Save the current position to the given file */
12497 SavePositionToFile (char *filename)
12502 if (strcmp(filename, "-") == 0) {
12503 return SavePosition(stdout, 0, NULL);
12505 f = fopen(filename, "a");
12507 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12508 DisplayError(buf, errno);
12511 safeStrCpy(buf, lastMsg, MSG_SIZ);
12512 DisplayMessage(_("Waiting for access to save file"), "");
12513 flock(fileno(f), LOCK_EX); // [HGM] lock
12514 DisplayMessage(_("Saving position"), "");
12515 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12516 SavePosition(f, 0, NULL);
12517 DisplayMessage(buf, "");
12523 /* Save the current position to the given open file and close the file */
12525 SavePosition (FILE *f, int dummy, char *dummy2)
12530 if (gameMode == EditPosition) EditPositionDone(TRUE);
12531 if (appData.oldSaveStyle) {
12532 tm = time((time_t *) NULL);
12534 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12536 fprintf(f, "[--------------\n");
12537 PrintPosition(f, currentMove);
12538 fprintf(f, "--------------]\n");
12540 fen = PositionToFEN(currentMove, NULL);
12541 fprintf(f, "%s\n", fen);
12549 ReloadCmailMsgEvent (int unregister)
12552 static char *inFilename = NULL;
12553 static char *outFilename;
12555 struct stat inbuf, outbuf;
12558 /* Any registered moves are unregistered if unregister is set, */
12559 /* i.e. invoked by the signal handler */
12561 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12562 cmailMoveRegistered[i] = FALSE;
12563 if (cmailCommentList[i] != NULL) {
12564 free(cmailCommentList[i]);
12565 cmailCommentList[i] = NULL;
12568 nCmailMovesRegistered = 0;
12571 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12572 cmailResult[i] = CMAIL_NOT_RESULT;
12576 if (inFilename == NULL) {
12577 /* Because the filenames are static they only get malloced once */
12578 /* and they never get freed */
12579 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12580 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12582 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12583 sprintf(outFilename, "%s.out", appData.cmailGameName);
12586 status = stat(outFilename, &outbuf);
12588 cmailMailedMove = FALSE;
12590 status = stat(inFilename, &inbuf);
12591 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12594 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12595 counts the games, notes how each one terminated, etc.
12597 It would be nice to remove this kludge and instead gather all
12598 the information while building the game list. (And to keep it
12599 in the game list nodes instead of having a bunch of fixed-size
12600 parallel arrays.) Note this will require getting each game's
12601 termination from the PGN tags, as the game list builder does
12602 not process the game moves. --mann
12604 cmailMsgLoaded = TRUE;
12605 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12607 /* Load first game in the file or popup game menu */
12608 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12610 #endif /* !WIN32 */
12618 char string[MSG_SIZ];
12620 if ( cmailMailedMove
12621 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12622 return TRUE; /* Allow free viewing */
12625 /* Unregister move to ensure that we don't leave RegisterMove */
12626 /* with the move registered when the conditions for registering no */
12628 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12629 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12630 nCmailMovesRegistered --;
12632 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12634 free(cmailCommentList[lastLoadGameNumber - 1]);
12635 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12639 if (cmailOldMove == -1) {
12640 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12644 if (currentMove > cmailOldMove + 1) {
12645 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12649 if (currentMove < cmailOldMove) {
12650 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12654 if (forwardMostMove > currentMove) {
12655 /* Silently truncate extra moves */
12659 if ( (currentMove == cmailOldMove + 1)
12660 || ( (currentMove == cmailOldMove)
12661 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12662 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12663 if (gameInfo.result != GameUnfinished) {
12664 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12667 if (commentList[currentMove] != NULL) {
12668 cmailCommentList[lastLoadGameNumber - 1]
12669 = StrSave(commentList[currentMove]);
12671 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12673 if (appData.debugMode)
12674 fprintf(debugFP, "Saving %s for game %d\n",
12675 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12677 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12679 f = fopen(string, "w");
12680 if (appData.oldSaveStyle) {
12681 SaveGameOldStyle(f); /* also closes the file */
12683 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12684 f = fopen(string, "w");
12685 SavePosition(f, 0, NULL); /* also closes the file */
12687 fprintf(f, "{--------------\n");
12688 PrintPosition(f, currentMove);
12689 fprintf(f, "--------------}\n\n");
12691 SaveGame(f, 0, NULL); /* also closes the file*/
12694 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12695 nCmailMovesRegistered ++;
12696 } else if (nCmailGames == 1) {
12697 DisplayError(_("You have not made a move yet"), 0);
12708 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12709 FILE *commandOutput;
12710 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12711 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12717 if (! cmailMsgLoaded) {
12718 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12722 if (nCmailGames == nCmailResults) {
12723 DisplayError(_("No unfinished games"), 0);
12727 #if CMAIL_PROHIBIT_REMAIL
12728 if (cmailMailedMove) {
12729 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);
12730 DisplayError(msg, 0);
12735 if (! (cmailMailedMove || RegisterMove())) return;
12737 if ( cmailMailedMove
12738 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12739 snprintf(string, MSG_SIZ, partCommandString,
12740 appData.debugMode ? " -v" : "", appData.cmailGameName);
12741 commandOutput = popen(string, "r");
12743 if (commandOutput == NULL) {
12744 DisplayError(_("Failed to invoke cmail"), 0);
12746 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12747 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12749 if (nBuffers > 1) {
12750 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12751 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12752 nBytes = MSG_SIZ - 1;
12754 (void) memcpy(msg, buffer, nBytes);
12756 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12758 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12759 cmailMailedMove = TRUE; /* Prevent >1 moves */
12762 for (i = 0; i < nCmailGames; i ++) {
12763 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12768 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12770 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12772 appData.cmailGameName,
12774 LoadGameFromFile(buffer, 1, buffer, FALSE);
12775 cmailMsgLoaded = FALSE;
12779 DisplayInformation(msg);
12780 pclose(commandOutput);
12783 if ((*cmailMsg) != '\0') {
12784 DisplayInformation(cmailMsg);
12789 #endif /* !WIN32 */
12798 int prependComma = 0;
12800 char string[MSG_SIZ]; /* Space for game-list */
12803 if (!cmailMsgLoaded) return "";
12805 if (cmailMailedMove) {
12806 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12808 /* Create a list of games left */
12809 snprintf(string, MSG_SIZ, "[");
12810 for (i = 0; i < nCmailGames; i ++) {
12811 if (! ( cmailMoveRegistered[i]
12812 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12813 if (prependComma) {
12814 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12816 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12820 strcat(string, number);
12823 strcat(string, "]");
12825 if (nCmailMovesRegistered + nCmailResults == 0) {
12826 switch (nCmailGames) {
12828 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12832 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12836 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12841 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12843 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12848 if (nCmailResults == nCmailGames) {
12849 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12851 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12856 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12868 if (gameMode == Training)
12869 SetTrainingModeOff();
12872 cmailMsgLoaded = FALSE;
12873 if (appData.icsActive) {
12874 SendToICS(ics_prefix);
12875 SendToICS("refresh\n");
12880 ExitEvent (int status)
12884 /* Give up on clean exit */
12888 /* Keep trying for clean exit */
12892 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12894 if (telnetISR != NULL) {
12895 RemoveInputSource(telnetISR);
12897 if (icsPR != NoProc) {
12898 DestroyChildProcess(icsPR, TRUE);
12901 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12902 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12904 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12905 /* make sure this other one finishes before killing it! */
12906 if(endingGame) { int count = 0;
12907 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12908 while(endingGame && count++ < 10) DoSleep(1);
12909 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12912 /* Kill off chess programs */
12913 if (first.pr != NoProc) {
12916 DoSleep( appData.delayBeforeQuit );
12917 SendToProgram("quit\n", &first);
12918 DoSleep( appData.delayAfterQuit );
12919 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12921 if (second.pr != NoProc) {
12922 DoSleep( appData.delayBeforeQuit );
12923 SendToProgram("quit\n", &second);
12924 DoSleep( appData.delayAfterQuit );
12925 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12927 if (first.isr != NULL) {
12928 RemoveInputSource(first.isr);
12930 if (second.isr != NULL) {
12931 RemoveInputSource(second.isr);
12934 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12935 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12937 ShutDownFrontEnd();
12944 if (appData.debugMode)
12945 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12949 if (gameMode == MachinePlaysWhite ||
12950 gameMode == MachinePlaysBlack) {
12953 DisplayBothClocks();
12955 if (gameMode == PlayFromGameFile) {
12956 if (appData.timeDelay >= 0)
12957 AutoPlayGameLoop();
12958 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12959 Reset(FALSE, TRUE);
12960 SendToICS(ics_prefix);
12961 SendToICS("refresh\n");
12962 } else if (currentMove < forwardMostMove) {
12963 ForwardInner(forwardMostMove);
12965 pauseExamInvalid = FALSE;
12967 switch (gameMode) {
12971 pauseExamForwardMostMove = forwardMostMove;
12972 pauseExamInvalid = FALSE;
12975 case IcsPlayingWhite:
12976 case IcsPlayingBlack:
12980 case PlayFromGameFile:
12981 (void) StopLoadGameTimer();
12985 case BeginningOfGame:
12986 if (appData.icsActive) return;
12987 /* else fall through */
12988 case MachinePlaysWhite:
12989 case MachinePlaysBlack:
12990 case TwoMachinesPlay:
12991 if (forwardMostMove == 0)
12992 return; /* don't pause if no one has moved */
12993 if ((gameMode == MachinePlaysWhite &&
12994 !WhiteOnMove(forwardMostMove)) ||
12995 (gameMode == MachinePlaysBlack &&
12996 WhiteOnMove(forwardMostMove))) {
13007 EditCommentEvent ()
13009 char title[MSG_SIZ];
13011 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13012 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13014 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13015 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13016 parseList[currentMove - 1]);
13019 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13026 char *tags = PGNTags(&gameInfo);
13028 EditTagsPopUp(tags, NULL);
13033 AnalyzeModeEvent ()
13035 if (appData.noChessProgram || gameMode == AnalyzeMode)
13038 if (gameMode != AnalyzeFile) {
13039 if (!appData.icsEngineAnalyze) {
13041 if (gameMode != EditGame) return;
13043 ResurrectChessProgram();
13044 SendToProgram("analyze\n", &first);
13045 first.analyzing = TRUE;
13046 /*first.maybeThinking = TRUE;*/
13047 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13048 EngineOutputPopUp();
13050 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13055 StartAnalysisClock();
13056 GetTimeMark(&lastNodeCountTime);
13061 AnalyzeFileEvent ()
13063 if (appData.noChessProgram || gameMode == AnalyzeFile)
13066 if (gameMode != AnalyzeMode) {
13068 if (gameMode != EditGame) return;
13069 ResurrectChessProgram();
13070 SendToProgram("analyze\n", &first);
13071 first.analyzing = TRUE;
13072 /*first.maybeThinking = TRUE;*/
13073 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13074 EngineOutputPopUp();
13076 gameMode = AnalyzeFile;
13081 StartAnalysisClock();
13082 GetTimeMark(&lastNodeCountTime);
13084 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13088 MachineWhiteEvent ()
13091 char *bookHit = NULL;
13093 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13097 if (gameMode == PlayFromGameFile ||
13098 gameMode == TwoMachinesPlay ||
13099 gameMode == Training ||
13100 gameMode == AnalyzeMode ||
13101 gameMode == EndOfGame)
13104 if (gameMode == EditPosition)
13105 EditPositionDone(TRUE);
13107 if (!WhiteOnMove(currentMove)) {
13108 DisplayError(_("It is not White's turn"), 0);
13112 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13115 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13116 gameMode == AnalyzeFile)
13119 ResurrectChessProgram(); /* in case it isn't running */
13120 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13121 gameMode = MachinePlaysWhite;
13124 gameMode = MachinePlaysWhite;
13128 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13130 if (first.sendName) {
13131 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13132 SendToProgram(buf, &first);
13134 if (first.sendTime) {
13135 if (first.useColors) {
13136 SendToProgram("black\n", &first); /*gnu kludge*/
13138 SendTimeRemaining(&first, TRUE);
13140 if (first.useColors) {
13141 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13143 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13144 SetMachineThinkingEnables();
13145 first.maybeThinking = TRUE;
13149 if (appData.autoFlipView && !flipView) {
13150 flipView = !flipView;
13151 DrawPosition(FALSE, NULL);
13152 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13155 if(bookHit) { // [HGM] book: simulate book reply
13156 static char bookMove[MSG_SIZ]; // a bit generous?
13158 programStats.nodes = programStats.depth = programStats.time =
13159 programStats.score = programStats.got_only_move = 0;
13160 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13162 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13163 strcat(bookMove, bookHit);
13164 HandleMachineMove(bookMove, &first);
13169 MachineBlackEvent ()
13172 char *bookHit = NULL;
13174 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13178 if (gameMode == PlayFromGameFile ||
13179 gameMode == TwoMachinesPlay ||
13180 gameMode == Training ||
13181 gameMode == AnalyzeMode ||
13182 gameMode == EndOfGame)
13185 if (gameMode == EditPosition)
13186 EditPositionDone(TRUE);
13188 if (WhiteOnMove(currentMove)) {
13189 DisplayError(_("It is not Black's turn"), 0);
13193 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13196 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13197 gameMode == AnalyzeFile)
13200 ResurrectChessProgram(); /* in case it isn't running */
13201 gameMode = MachinePlaysBlack;
13205 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13207 if (first.sendName) {
13208 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13209 SendToProgram(buf, &first);
13211 if (first.sendTime) {
13212 if (first.useColors) {
13213 SendToProgram("white\n", &first); /*gnu kludge*/
13215 SendTimeRemaining(&first, FALSE);
13217 if (first.useColors) {
13218 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13220 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13221 SetMachineThinkingEnables();
13222 first.maybeThinking = TRUE;
13225 if (appData.autoFlipView && flipView) {
13226 flipView = !flipView;
13227 DrawPosition(FALSE, NULL);
13228 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13230 if(bookHit) { // [HGM] book: simulate book reply
13231 static char bookMove[MSG_SIZ]; // a bit generous?
13233 programStats.nodes = programStats.depth = programStats.time =
13234 programStats.score = programStats.got_only_move = 0;
13235 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13237 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13238 strcat(bookMove, bookHit);
13239 HandleMachineMove(bookMove, &first);
13245 DisplayTwoMachinesTitle ()
13248 if (appData.matchGames > 0) {
13249 if(appData.tourneyFile[0]) {
13250 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13251 gameInfo.white, _("vs."), gameInfo.black,
13252 nextGame+1, appData.matchGames+1,
13253 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13255 if (first.twoMachinesColor[0] == 'w') {
13256 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13257 gameInfo.white, _("vs."), gameInfo.black,
13258 first.matchWins, second.matchWins,
13259 matchGame - 1 - (first.matchWins + second.matchWins));
13261 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13262 gameInfo.white, _("vs."), gameInfo.black,
13263 second.matchWins, first.matchWins,
13264 matchGame - 1 - (first.matchWins + second.matchWins));
13267 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13273 SettingsMenuIfReady ()
13275 if (second.lastPing != second.lastPong) {
13276 DisplayMessage("", _("Waiting for second chess program"));
13277 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13281 DisplayMessage("", "");
13282 SettingsPopUp(&second);
13286 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13289 if (cps->pr == NoProc) {
13290 StartChessProgram(cps);
13291 if (cps->protocolVersion == 1) {
13294 /* kludge: allow timeout for initial "feature" command */
13296 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13297 DisplayMessage("", buf);
13298 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13306 TwoMachinesEvent P((void))
13310 ChessProgramState *onmove;
13311 char *bookHit = NULL;
13312 static int stalling = 0;
13316 if (appData.noChessProgram) return;
13318 switch (gameMode) {
13319 case TwoMachinesPlay:
13321 case MachinePlaysWhite:
13322 case MachinePlaysBlack:
13323 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13324 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13328 case BeginningOfGame:
13329 case PlayFromGameFile:
13332 if (gameMode != EditGame) return;
13335 EditPositionDone(TRUE);
13346 // forwardMostMove = currentMove;
13347 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13349 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13351 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13352 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13353 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13357 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13358 SendToProgram("force\n", &second);
13360 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13363 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13364 if(appData.matchPause>10000 || appData.matchPause<10)
13365 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13366 wait = SubtractTimeMarks(&now, &pauseStart);
13367 if(wait < appData.matchPause) {
13368 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13372 DisplayMessage("", "");
13373 if (startedFromSetupPosition) {
13374 SendBoard(&second, backwardMostMove);
13375 if (appData.debugMode) {
13376 fprintf(debugFP, "Two Machines\n");
13379 for (i = backwardMostMove; i < forwardMostMove; i++) {
13380 SendMoveToProgram(i, &second);
13383 gameMode = TwoMachinesPlay;
13385 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13387 DisplayTwoMachinesTitle();
13389 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13394 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13395 SendToProgram(first.computerString, &first);
13396 if (first.sendName) {
13397 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13398 SendToProgram(buf, &first);
13400 SendToProgram(second.computerString, &second);
13401 if (second.sendName) {
13402 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13403 SendToProgram(buf, &second);
13407 if (!first.sendTime || !second.sendTime) {
13408 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13409 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13411 if (onmove->sendTime) {
13412 if (onmove->useColors) {
13413 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13415 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13417 if (onmove->useColors) {
13418 SendToProgram(onmove->twoMachinesColor, onmove);
13420 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13421 // SendToProgram("go\n", onmove);
13422 onmove->maybeThinking = TRUE;
13423 SetMachineThinkingEnables();
13427 if(bookHit) { // [HGM] book: simulate book reply
13428 static char bookMove[MSG_SIZ]; // a bit generous?
13430 programStats.nodes = programStats.depth = programStats.time =
13431 programStats.score = programStats.got_only_move = 0;
13432 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13434 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13435 strcat(bookMove, bookHit);
13436 savedMessage = bookMove; // args for deferred call
13437 savedState = onmove;
13438 ScheduleDelayedEvent(DeferredBookMove, 1);
13445 if (gameMode == Training) {
13446 SetTrainingModeOff();
13447 gameMode = PlayFromGameFile;
13448 DisplayMessage("", _("Training mode off"));
13450 gameMode = Training;
13451 animateTraining = appData.animate;
13453 /* make sure we are not already at the end of the game */
13454 if (currentMove < forwardMostMove) {
13455 SetTrainingModeOn();
13456 DisplayMessage("", _("Training mode on"));
13458 gameMode = PlayFromGameFile;
13459 DisplayError(_("Already at end of game"), 0);
13468 if (!appData.icsActive) return;
13469 switch (gameMode) {
13470 case IcsPlayingWhite:
13471 case IcsPlayingBlack:
13474 case BeginningOfGame:
13482 EditPositionDone(TRUE);
13495 gameMode = IcsIdle;
13505 switch (gameMode) {
13507 SetTrainingModeOff();
13509 case MachinePlaysWhite:
13510 case MachinePlaysBlack:
13511 case BeginningOfGame:
13512 SendToProgram("force\n", &first);
13513 SetUserThinkingEnables();
13515 case PlayFromGameFile:
13516 (void) StopLoadGameTimer();
13517 if (gameFileFP != NULL) {
13522 EditPositionDone(TRUE);
13527 SendToProgram("force\n", &first);
13529 case TwoMachinesPlay:
13530 GameEnds(EndOfFile, NULL, GE_PLAYER);
13531 ResurrectChessProgram();
13532 SetUserThinkingEnables();
13535 ResurrectChessProgram();
13537 case IcsPlayingBlack:
13538 case IcsPlayingWhite:
13539 DisplayError(_("Warning: You are still playing a game"), 0);
13542 DisplayError(_("Warning: You are still observing a game"), 0);
13545 DisplayError(_("Warning: You are still examining a game"), 0);
13556 first.offeredDraw = second.offeredDraw = 0;
13558 if (gameMode == PlayFromGameFile) {
13559 whiteTimeRemaining = timeRemaining[0][currentMove];
13560 blackTimeRemaining = timeRemaining[1][currentMove];
13564 if (gameMode == MachinePlaysWhite ||
13565 gameMode == MachinePlaysBlack ||
13566 gameMode == TwoMachinesPlay ||
13567 gameMode == EndOfGame) {
13568 i = forwardMostMove;
13569 while (i > currentMove) {
13570 SendToProgram("undo\n", &first);
13573 if(!adjustedClock) {
13574 whiteTimeRemaining = timeRemaining[0][currentMove];
13575 blackTimeRemaining = timeRemaining[1][currentMove];
13576 DisplayBothClocks();
13578 if (whiteFlag || blackFlag) {
13579 whiteFlag = blackFlag = 0;
13584 gameMode = EditGame;
13591 EditPositionEvent ()
13593 if (gameMode == EditPosition) {
13599 if (gameMode != EditGame) return;
13601 gameMode = EditPosition;
13604 if (currentMove > 0)
13605 CopyBoard(boards[0], boards[currentMove]);
13607 blackPlaysFirst = !WhiteOnMove(currentMove);
13609 currentMove = forwardMostMove = backwardMostMove = 0;
13610 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13617 /* [DM] icsEngineAnalyze - possible call from other functions */
13618 if (appData.icsEngineAnalyze) {
13619 appData.icsEngineAnalyze = FALSE;
13621 DisplayMessage("",_("Close ICS engine analyze..."));
13623 if (first.analysisSupport && first.analyzing) {
13624 SendToProgram("exit\n", &first);
13625 first.analyzing = FALSE;
13627 thinkOutput[0] = NULLCHAR;
13631 EditPositionDone (Boolean fakeRights)
13633 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13635 startedFromSetupPosition = TRUE;
13636 InitChessProgram(&first, FALSE);
13637 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13638 boards[0][EP_STATUS] = EP_NONE;
13639 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13640 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13641 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13642 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13643 } else boards[0][CASTLING][2] = NoRights;
13644 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13645 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13646 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13647 } else boards[0][CASTLING][5] = NoRights;
13649 SendToProgram("force\n", &first);
13650 if (blackPlaysFirst) {
13651 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13652 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13653 currentMove = forwardMostMove = backwardMostMove = 1;
13654 CopyBoard(boards[1], boards[0]);
13656 currentMove = forwardMostMove = backwardMostMove = 0;
13658 SendBoard(&first, forwardMostMove);
13659 if (appData.debugMode) {
13660 fprintf(debugFP, "EditPosDone\n");
13663 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13664 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13665 gameMode = EditGame;
13667 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13668 ClearHighlights(); /* [AS] */
13671 /* Pause for `ms' milliseconds */
13672 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13674 TimeDelay (long ms)
13681 } while (SubtractTimeMarks(&m2, &m1) < ms);
13684 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13686 SendMultiLineToICS (char *buf)
13688 char temp[MSG_SIZ+1], *p;
13695 strncpy(temp, buf, len);
13700 if (*p == '\n' || *p == '\r')
13705 strcat(temp, "\n");
13707 SendToPlayer(temp, strlen(temp));
13711 SetWhiteToPlayEvent ()
13713 if (gameMode == EditPosition) {
13714 blackPlaysFirst = FALSE;
13715 DisplayBothClocks(); /* works because currentMove is 0 */
13716 } else if (gameMode == IcsExamining) {
13717 SendToICS(ics_prefix);
13718 SendToICS("tomove white\n");
13723 SetBlackToPlayEvent ()
13725 if (gameMode == EditPosition) {
13726 blackPlaysFirst = TRUE;
13727 currentMove = 1; /* kludge */
13728 DisplayBothClocks();
13730 } else if (gameMode == IcsExamining) {
13731 SendToICS(ics_prefix);
13732 SendToICS("tomove black\n");
13737 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13740 ChessSquare piece = boards[0][y][x];
13742 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13744 switch (selection) {
13746 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13747 SendToICS(ics_prefix);
13748 SendToICS("bsetup clear\n");
13749 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13750 SendToICS(ics_prefix);
13751 SendToICS("clearboard\n");
13753 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13754 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13755 for (y = 0; y < BOARD_HEIGHT; y++) {
13756 if (gameMode == IcsExamining) {
13757 if (boards[currentMove][y][x] != EmptySquare) {
13758 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13763 boards[0][y][x] = p;
13768 if (gameMode == EditPosition) {
13769 DrawPosition(FALSE, boards[0]);
13774 SetWhiteToPlayEvent();
13778 SetBlackToPlayEvent();
13782 if (gameMode == IcsExamining) {
13783 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13784 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13787 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13788 if(x == BOARD_LEFT-2) {
13789 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13790 boards[0][y][1] = 0;
13792 if(x == BOARD_RGHT+1) {
13793 if(y >= gameInfo.holdingsSize) break;
13794 boards[0][y][BOARD_WIDTH-2] = 0;
13797 boards[0][y][x] = EmptySquare;
13798 DrawPosition(FALSE, boards[0]);
13803 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13804 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13805 selection = (ChessSquare) (PROMOTED piece);
13806 } else if(piece == EmptySquare) selection = WhiteSilver;
13807 else selection = (ChessSquare)((int)piece - 1);
13811 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13812 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13813 selection = (ChessSquare) (DEMOTED piece);
13814 } else if(piece == EmptySquare) selection = BlackSilver;
13815 else selection = (ChessSquare)((int)piece + 1);
13820 if(gameInfo.variant == VariantShatranj ||
13821 gameInfo.variant == VariantXiangqi ||
13822 gameInfo.variant == VariantCourier ||
13823 gameInfo.variant == VariantMakruk )
13824 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13829 if(gameInfo.variant == VariantXiangqi)
13830 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13831 if(gameInfo.variant == VariantKnightmate)
13832 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13835 if (gameMode == IcsExamining) {
13836 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13837 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13838 PieceToChar(selection), AAA + x, ONE + y);
13841 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13843 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13844 n = PieceToNumber(selection - BlackPawn);
13845 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13846 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13847 boards[0][BOARD_HEIGHT-1-n][1]++;
13849 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13850 n = PieceToNumber(selection);
13851 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13852 boards[0][n][BOARD_WIDTH-1] = selection;
13853 boards[0][n][BOARD_WIDTH-2]++;
13856 boards[0][y][x] = selection;
13857 DrawPosition(TRUE, boards[0]);
13865 DropMenuEvent (ChessSquare selection, int x, int y)
13867 ChessMove moveType;
13869 switch (gameMode) {
13870 case IcsPlayingWhite:
13871 case MachinePlaysBlack:
13872 if (!WhiteOnMove(currentMove)) {
13873 DisplayMoveError(_("It is Black's turn"));
13876 moveType = WhiteDrop;
13878 case IcsPlayingBlack:
13879 case MachinePlaysWhite:
13880 if (WhiteOnMove(currentMove)) {
13881 DisplayMoveError(_("It is White's turn"));
13884 moveType = BlackDrop;
13887 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13893 if (moveType == BlackDrop && selection < BlackPawn) {
13894 selection = (ChessSquare) ((int) selection
13895 + (int) BlackPawn - (int) WhitePawn);
13897 if (boards[currentMove][y][x] != EmptySquare) {
13898 DisplayMoveError(_("That square is occupied"));
13902 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13908 /* Accept a pending offer of any kind from opponent */
13910 if (appData.icsActive) {
13911 SendToICS(ics_prefix);
13912 SendToICS("accept\n");
13913 } else if (cmailMsgLoaded) {
13914 if (currentMove == cmailOldMove &&
13915 commentList[cmailOldMove] != NULL &&
13916 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13917 "Black offers a draw" : "White offers a draw")) {
13919 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13920 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13922 DisplayError(_("There is no pending offer on this move"), 0);
13923 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13926 /* Not used for offers from chess program */
13933 /* Decline a pending offer of any kind from opponent */
13935 if (appData.icsActive) {
13936 SendToICS(ics_prefix);
13937 SendToICS("decline\n");
13938 } else if (cmailMsgLoaded) {
13939 if (currentMove == cmailOldMove &&
13940 commentList[cmailOldMove] != NULL &&
13941 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13942 "Black offers a draw" : "White offers a draw")) {
13944 AppendComment(cmailOldMove, "Draw declined", TRUE);
13945 DisplayComment(cmailOldMove - 1, "Draw declined");
13948 DisplayError(_("There is no pending offer on this move"), 0);
13951 /* Not used for offers from chess program */
13958 /* Issue ICS rematch command */
13959 if (appData.icsActive) {
13960 SendToICS(ics_prefix);
13961 SendToICS("rematch\n");
13968 /* Call your opponent's flag (claim a win on time) */
13969 if (appData.icsActive) {
13970 SendToICS(ics_prefix);
13971 SendToICS("flag\n");
13973 switch (gameMode) {
13976 case MachinePlaysWhite:
13979 GameEnds(GameIsDrawn, "Both players ran out of time",
13982 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13984 DisplayError(_("Your opponent is not out of time"), 0);
13987 case MachinePlaysBlack:
13990 GameEnds(GameIsDrawn, "Both players ran out of time",
13993 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13995 DisplayError(_("Your opponent is not out of time"), 0);
14003 ClockClick (int which)
14004 { // [HGM] code moved to back-end from winboard.c
14005 if(which) { // black clock
14006 if (gameMode == EditPosition || gameMode == IcsExamining) {
14007 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14008 SetBlackToPlayEvent();
14009 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14010 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14011 } else if (shiftKey) {
14012 AdjustClock(which, -1);
14013 } else if (gameMode == IcsPlayingWhite ||
14014 gameMode == MachinePlaysBlack) {
14017 } else { // white clock
14018 if (gameMode == EditPosition || gameMode == IcsExamining) {
14019 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14020 SetWhiteToPlayEvent();
14021 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14022 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14023 } else if (shiftKey) {
14024 AdjustClock(which, -1);
14025 } else if (gameMode == IcsPlayingBlack ||
14026 gameMode == MachinePlaysWhite) {
14035 /* Offer draw or accept pending draw offer from opponent */
14037 if (appData.icsActive) {
14038 /* Note: tournament rules require draw offers to be
14039 made after you make your move but before you punch
14040 your clock. Currently ICS doesn't let you do that;
14041 instead, you immediately punch your clock after making
14042 a move, but you can offer a draw at any time. */
14044 SendToICS(ics_prefix);
14045 SendToICS("draw\n");
14046 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14047 } else if (cmailMsgLoaded) {
14048 if (currentMove == cmailOldMove &&
14049 commentList[cmailOldMove] != NULL &&
14050 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14051 "Black offers a draw" : "White offers a draw")) {
14052 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14053 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14054 } else if (currentMove == cmailOldMove + 1) {
14055 char *offer = WhiteOnMove(cmailOldMove) ?
14056 "White offers a draw" : "Black offers a draw";
14057 AppendComment(currentMove, offer, TRUE);
14058 DisplayComment(currentMove - 1, offer);
14059 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14061 DisplayError(_("You must make your move before offering a draw"), 0);
14062 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14064 } else if (first.offeredDraw) {
14065 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14067 if (first.sendDrawOffers) {
14068 SendToProgram("draw\n", &first);
14069 userOfferedDraw = TRUE;
14077 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14079 if (appData.icsActive) {
14080 SendToICS(ics_prefix);
14081 SendToICS("adjourn\n");
14083 /* Currently GNU Chess doesn't offer or accept Adjourns */
14091 /* Offer Abort or accept pending Abort offer from opponent */
14093 if (appData.icsActive) {
14094 SendToICS(ics_prefix);
14095 SendToICS("abort\n");
14097 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14104 /* Resign. You can do this even if it's not your turn. */
14106 if (appData.icsActive) {
14107 SendToICS(ics_prefix);
14108 SendToICS("resign\n");
14110 switch (gameMode) {
14111 case MachinePlaysWhite:
14112 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14114 case MachinePlaysBlack:
14115 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14118 if (cmailMsgLoaded) {
14120 if (WhiteOnMove(cmailOldMove)) {
14121 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14123 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14125 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14136 StopObservingEvent ()
14138 /* Stop observing current games */
14139 SendToICS(ics_prefix);
14140 SendToICS("unobserve\n");
14144 StopExaminingEvent ()
14146 /* Stop observing current game */
14147 SendToICS(ics_prefix);
14148 SendToICS("unexamine\n");
14152 ForwardInner (int target)
14156 if (appData.debugMode)
14157 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14158 target, currentMove, forwardMostMove);
14160 if (gameMode == EditPosition)
14163 MarkTargetSquares(1);
14165 if (gameMode == PlayFromGameFile && !pausing)
14168 if (gameMode == IcsExamining && pausing)
14169 limit = pauseExamForwardMostMove;
14171 limit = forwardMostMove;
14173 if (target > limit) target = limit;
14175 if (target > 0 && moveList[target - 1][0]) {
14176 int fromX, fromY, toX, toY;
14177 toX = moveList[target - 1][2] - AAA;
14178 toY = moveList[target - 1][3] - ONE;
14179 if (moveList[target - 1][1] == '@') {
14180 if (appData.highlightLastMove) {
14181 SetHighlights(-1, -1, toX, toY);
14184 fromX = moveList[target - 1][0] - AAA;
14185 fromY = moveList[target - 1][1] - ONE;
14186 if (target == currentMove + 1) {
14187 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14189 if (appData.highlightLastMove) {
14190 SetHighlights(fromX, fromY, toX, toY);
14194 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14195 gameMode == Training || gameMode == PlayFromGameFile ||
14196 gameMode == AnalyzeFile) {
14197 while (currentMove < target) {
14198 SendMoveToProgram(currentMove++, &first);
14201 currentMove = target;
14204 if (gameMode == EditGame || gameMode == EndOfGame) {
14205 whiteTimeRemaining = timeRemaining[0][currentMove];
14206 blackTimeRemaining = timeRemaining[1][currentMove];
14208 DisplayBothClocks();
14209 DisplayMove(currentMove - 1);
14210 DrawPosition(FALSE, boards[currentMove]);
14211 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14212 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14213 DisplayComment(currentMove - 1, commentList[currentMove]);
14221 if (gameMode == IcsExamining && !pausing) {
14222 SendToICS(ics_prefix);
14223 SendToICS("forward\n");
14225 ForwardInner(currentMove + 1);
14232 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14233 /* to optimze, we temporarily turn off analysis mode while we feed
14234 * the remaining moves to the engine. Otherwise we get analysis output
14237 if (first.analysisSupport) {
14238 SendToProgram("exit\nforce\n", &first);
14239 first.analyzing = FALSE;
14243 if (gameMode == IcsExamining && !pausing) {
14244 SendToICS(ics_prefix);
14245 SendToICS("forward 999999\n");
14247 ForwardInner(forwardMostMove);
14250 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14251 /* we have fed all the moves, so reactivate analysis mode */
14252 SendToProgram("analyze\n", &first);
14253 first.analyzing = TRUE;
14254 /*first.maybeThinking = TRUE;*/
14255 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14260 BackwardInner (int target)
14262 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14264 if (appData.debugMode)
14265 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14266 target, currentMove, forwardMostMove);
14268 if (gameMode == EditPosition) return;
14269 MarkTargetSquares(1);
14270 if (currentMove <= backwardMostMove) {
14272 DrawPosition(full_redraw, boards[currentMove]);
14275 if (gameMode == PlayFromGameFile && !pausing)
14278 if (moveList[target][0]) {
14279 int fromX, fromY, toX, toY;
14280 toX = moveList[target][2] - AAA;
14281 toY = moveList[target][3] - ONE;
14282 if (moveList[target][1] == '@') {
14283 if (appData.highlightLastMove) {
14284 SetHighlights(-1, -1, toX, toY);
14287 fromX = moveList[target][0] - AAA;
14288 fromY = moveList[target][1] - ONE;
14289 if (target == currentMove - 1) {
14290 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14292 if (appData.highlightLastMove) {
14293 SetHighlights(fromX, fromY, toX, toY);
14297 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14298 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14299 while (currentMove > target) {
14300 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14301 // null move cannot be undone. Reload program with move history before it.
14303 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14304 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14306 SendBoard(&first, i);
14307 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14310 SendToProgram("undo\n", &first);
14314 currentMove = target;
14317 if (gameMode == EditGame || gameMode == EndOfGame) {
14318 whiteTimeRemaining = timeRemaining[0][currentMove];
14319 blackTimeRemaining = timeRemaining[1][currentMove];
14321 DisplayBothClocks();
14322 DisplayMove(currentMove - 1);
14323 DrawPosition(full_redraw, boards[currentMove]);
14324 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14325 // [HGM] PV info: routine tests if comment empty
14326 DisplayComment(currentMove - 1, commentList[currentMove]);
14332 if (gameMode == IcsExamining && !pausing) {
14333 SendToICS(ics_prefix);
14334 SendToICS("backward\n");
14336 BackwardInner(currentMove - 1);
14343 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14344 /* to optimize, we temporarily turn off analysis mode while we undo
14345 * all the moves. Otherwise we get analysis output after each undo.
14347 if (first.analysisSupport) {
14348 SendToProgram("exit\nforce\n", &first);
14349 first.analyzing = FALSE;
14353 if (gameMode == IcsExamining && !pausing) {
14354 SendToICS(ics_prefix);
14355 SendToICS("backward 999999\n");
14357 BackwardInner(backwardMostMove);
14360 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14361 /* we have fed all the moves, so reactivate analysis mode */
14362 SendToProgram("analyze\n", &first);
14363 first.analyzing = TRUE;
14364 /*first.maybeThinking = TRUE;*/
14365 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14372 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14373 if (to >= forwardMostMove) to = forwardMostMove;
14374 if (to <= backwardMostMove) to = backwardMostMove;
14375 if (to < currentMove) {
14383 RevertEvent (Boolean annotate)
14385 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14388 if (gameMode != IcsExamining) {
14389 DisplayError(_("You are not examining a game"), 0);
14393 DisplayError(_("You can't revert while pausing"), 0);
14396 SendToICS(ics_prefix);
14397 SendToICS("revert\n");
14401 RetractMoveEvent ()
14403 switch (gameMode) {
14404 case MachinePlaysWhite:
14405 case MachinePlaysBlack:
14406 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14407 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14410 if (forwardMostMove < 2) return;
14411 currentMove = forwardMostMove = forwardMostMove - 2;
14412 whiteTimeRemaining = timeRemaining[0][currentMove];
14413 blackTimeRemaining = timeRemaining[1][currentMove];
14414 DisplayBothClocks();
14415 DisplayMove(currentMove - 1);
14416 ClearHighlights();/*!! could figure this out*/
14417 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14418 SendToProgram("remove\n", &first);
14419 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14422 case BeginningOfGame:
14426 case IcsPlayingWhite:
14427 case IcsPlayingBlack:
14428 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14429 SendToICS(ics_prefix);
14430 SendToICS("takeback 2\n");
14432 SendToICS(ics_prefix);
14433 SendToICS("takeback 1\n");
14442 ChessProgramState *cps;
14444 switch (gameMode) {
14445 case MachinePlaysWhite:
14446 if (!WhiteOnMove(forwardMostMove)) {
14447 DisplayError(_("It is your turn"), 0);
14452 case MachinePlaysBlack:
14453 if (WhiteOnMove(forwardMostMove)) {
14454 DisplayError(_("It is your turn"), 0);
14459 case TwoMachinesPlay:
14460 if (WhiteOnMove(forwardMostMove) ==
14461 (first.twoMachinesColor[0] == 'w')) {
14467 case BeginningOfGame:
14471 SendToProgram("?\n", cps);
14475 TruncateGameEvent ()
14478 if (gameMode != EditGame) return;
14485 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14486 if (forwardMostMove > currentMove) {
14487 if (gameInfo.resultDetails != NULL) {
14488 free(gameInfo.resultDetails);
14489 gameInfo.resultDetails = NULL;
14490 gameInfo.result = GameUnfinished;
14492 forwardMostMove = currentMove;
14493 HistorySet(parseList, backwardMostMove, forwardMostMove,
14501 if (appData.noChessProgram) return;
14502 switch (gameMode) {
14503 case MachinePlaysWhite:
14504 if (WhiteOnMove(forwardMostMove)) {
14505 DisplayError(_("Wait until your turn"), 0);
14509 case BeginningOfGame:
14510 case MachinePlaysBlack:
14511 if (!WhiteOnMove(forwardMostMove)) {
14512 DisplayError(_("Wait until your turn"), 0);
14517 DisplayError(_("No hint available"), 0);
14520 SendToProgram("hint\n", &first);
14521 hintRequested = TRUE;
14527 if (appData.noChessProgram) return;
14528 switch (gameMode) {
14529 case MachinePlaysWhite:
14530 if (WhiteOnMove(forwardMostMove)) {
14531 DisplayError(_("Wait until your turn"), 0);
14535 case BeginningOfGame:
14536 case MachinePlaysBlack:
14537 if (!WhiteOnMove(forwardMostMove)) {
14538 DisplayError(_("Wait until your turn"), 0);
14543 EditPositionDone(TRUE);
14545 case TwoMachinesPlay:
14550 SendToProgram("bk\n", &first);
14551 bookOutput[0] = NULLCHAR;
14552 bookRequested = TRUE;
14558 char *tags = PGNTags(&gameInfo);
14559 TagsPopUp(tags, CmailMsg());
14563 /* end button procedures */
14566 PrintPosition (FILE *fp, int move)
14570 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14571 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14572 char c = PieceToChar(boards[move][i][j]);
14573 fputc(c == 'x' ? '.' : c, fp);
14574 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14577 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14578 fprintf(fp, "white to play\n");
14580 fprintf(fp, "black to play\n");
14584 PrintOpponents (FILE *fp)
14586 if (gameInfo.white != NULL) {
14587 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14593 /* Find last component of program's own name, using some heuristics */
14595 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14598 int local = (strcmp(host, "localhost") == 0);
14599 while (!local && (p = strchr(prog, ';')) != NULL) {
14601 while (*p == ' ') p++;
14604 if (*prog == '"' || *prog == '\'') {
14605 q = strchr(prog + 1, *prog);
14607 q = strchr(prog, ' ');
14609 if (q == NULL) q = prog + strlen(prog);
14611 while (p >= prog && *p != '/' && *p != '\\') p--;
14613 if(p == prog && *p == '"') p++;
14614 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14615 memcpy(buf, p, q - p);
14616 buf[q - p] = NULLCHAR;
14624 TimeControlTagValue ()
14627 if (!appData.clockMode) {
14628 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14629 } else if (movesPerSession > 0) {
14630 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14631 } else if (timeIncrement == 0) {
14632 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14634 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14636 return StrSave(buf);
14642 /* This routine is used only for certain modes */
14643 VariantClass v = gameInfo.variant;
14644 ChessMove r = GameUnfinished;
14647 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14648 r = gameInfo.result;
14649 p = gameInfo.resultDetails;
14650 gameInfo.resultDetails = NULL;
14652 ClearGameInfo(&gameInfo);
14653 gameInfo.variant = v;
14655 switch (gameMode) {
14656 case MachinePlaysWhite:
14657 gameInfo.event = StrSave( appData.pgnEventHeader );
14658 gameInfo.site = StrSave(HostName());
14659 gameInfo.date = PGNDate();
14660 gameInfo.round = StrSave("-");
14661 gameInfo.white = StrSave(first.tidy);
14662 gameInfo.black = StrSave(UserName());
14663 gameInfo.timeControl = TimeControlTagValue();
14666 case MachinePlaysBlack:
14667 gameInfo.event = StrSave( appData.pgnEventHeader );
14668 gameInfo.site = StrSave(HostName());
14669 gameInfo.date = PGNDate();
14670 gameInfo.round = StrSave("-");
14671 gameInfo.white = StrSave(UserName());
14672 gameInfo.black = StrSave(first.tidy);
14673 gameInfo.timeControl = TimeControlTagValue();
14676 case TwoMachinesPlay:
14677 gameInfo.event = StrSave( appData.pgnEventHeader );
14678 gameInfo.site = StrSave(HostName());
14679 gameInfo.date = PGNDate();
14682 snprintf(buf, MSG_SIZ, "%d", roundNr);
14683 gameInfo.round = StrSave(buf);
14685 gameInfo.round = StrSave("-");
14687 if (first.twoMachinesColor[0] == 'w') {
14688 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14689 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14691 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14692 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14694 gameInfo.timeControl = TimeControlTagValue();
14698 gameInfo.event = StrSave("Edited game");
14699 gameInfo.site = StrSave(HostName());
14700 gameInfo.date = PGNDate();
14701 gameInfo.round = StrSave("-");
14702 gameInfo.white = StrSave("-");
14703 gameInfo.black = StrSave("-");
14704 gameInfo.result = r;
14705 gameInfo.resultDetails = p;
14709 gameInfo.event = StrSave("Edited position");
14710 gameInfo.site = StrSave(HostName());
14711 gameInfo.date = PGNDate();
14712 gameInfo.round = StrSave("-");
14713 gameInfo.white = StrSave("-");
14714 gameInfo.black = StrSave("-");
14717 case IcsPlayingWhite:
14718 case IcsPlayingBlack:
14723 case PlayFromGameFile:
14724 gameInfo.event = StrSave("Game from non-PGN file");
14725 gameInfo.site = StrSave(HostName());
14726 gameInfo.date = PGNDate();
14727 gameInfo.round = StrSave("-");
14728 gameInfo.white = StrSave("?");
14729 gameInfo.black = StrSave("?");
14738 ReplaceComment (int index, char *text)
14744 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14745 pvInfoList[index-1].depth == len &&
14746 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14747 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14748 while (*text == '\n') text++;
14749 len = strlen(text);
14750 while (len > 0 && text[len - 1] == '\n') len--;
14752 if (commentList[index] != NULL)
14753 free(commentList[index]);
14756 commentList[index] = NULL;
14759 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14760 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14761 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14762 commentList[index] = (char *) malloc(len + 2);
14763 strncpy(commentList[index], text, len);
14764 commentList[index][len] = '\n';
14765 commentList[index][len + 1] = NULLCHAR;
14767 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14769 commentList[index] = (char *) malloc(len + 7);
14770 safeStrCpy(commentList[index], "{\n", 3);
14771 safeStrCpy(commentList[index]+2, text, len+1);
14772 commentList[index][len+2] = NULLCHAR;
14773 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14774 strcat(commentList[index], "\n}\n");
14779 CrushCRs (char *text)
14787 if (ch == '\r') continue;
14789 } while (ch != '\0');
14793 AppendComment (int index, char *text, Boolean addBraces)
14794 /* addBraces tells if we should add {} */
14799 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14800 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14803 while (*text == '\n') text++;
14804 len = strlen(text);
14805 while (len > 0 && text[len - 1] == '\n') len--;
14806 text[len] = NULLCHAR;
14808 if (len == 0) return;
14810 if (commentList[index] != NULL) {
14811 Boolean addClosingBrace = addBraces;
14812 old = commentList[index];
14813 oldlen = strlen(old);
14814 while(commentList[index][oldlen-1] == '\n')
14815 commentList[index][--oldlen] = NULLCHAR;
14816 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14817 safeStrCpy(commentList[index], old, oldlen + len + 6);
14819 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14820 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14821 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14822 while (*text == '\n') { text++; len--; }
14823 commentList[index][--oldlen] = NULLCHAR;
14825 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14826 else strcat(commentList[index], "\n");
14827 strcat(commentList[index], text);
14828 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14829 else strcat(commentList[index], "\n");
14831 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14833 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14834 else commentList[index][0] = NULLCHAR;
14835 strcat(commentList[index], text);
14836 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14837 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14842 FindStr (char * text, char * sub_text)
14844 char * result = strstr( text, sub_text );
14846 if( result != NULL ) {
14847 result += strlen( sub_text );
14853 /* [AS] Try to extract PV info from PGN comment */
14854 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14856 GetInfoFromComment (int index, char * text)
14858 char * sep = text, *p;
14860 if( text != NULL && index > 0 ) {
14863 int time = -1, sec = 0, deci;
14864 char * s_eval = FindStr( text, "[%eval " );
14865 char * s_emt = FindStr( text, "[%emt " );
14867 if( s_eval != NULL || s_emt != NULL ) {
14871 if( s_eval != NULL ) {
14872 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14876 if( delim != ']' ) {
14881 if( s_emt != NULL ) {
14886 /* We expect something like: [+|-]nnn.nn/dd */
14889 if(*text != '{') return text; // [HGM] braces: must be normal comment
14891 sep = strchr( text, '/' );
14892 if( sep == NULL || sep < (text+4) ) {
14897 if(p[1] == '(') { // comment starts with PV
14898 p = strchr(p, ')'); // locate end of PV
14899 if(p == NULL || sep < p+5) return text;
14900 // at this point we have something like "{(.*) +0.23/6 ..."
14901 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14902 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14903 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14905 time = -1; sec = -1; deci = -1;
14906 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14907 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14908 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14909 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14913 if( score_lo < 0 || score_lo >= 100 ) {
14917 if(sec >= 0) time = 600*time + 10*sec; else
14918 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14920 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14922 /* [HGM] PV time: now locate end of PV info */
14923 while( *++sep >= '0' && *sep <= '9'); // strip depth
14925 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14927 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14929 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14930 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14941 pvInfoList[index-1].depth = depth;
14942 pvInfoList[index-1].score = score;
14943 pvInfoList[index-1].time = 10*time; // centi-sec
14944 if(*sep == '}') *sep = 0; else *--sep = '{';
14945 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14951 SendToProgram (char *message, ChessProgramState *cps)
14953 int count, outCount, error;
14956 if (cps->pr == NoProc) return;
14959 if (appData.debugMode) {
14962 fprintf(debugFP, "%ld >%-6s: %s",
14963 SubtractTimeMarks(&now, &programStartTime),
14964 cps->which, message);
14967 count = strlen(message);
14968 outCount = OutputToProcess(cps->pr, message, count, &error);
14969 if (outCount < count && !exiting
14970 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14971 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14972 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14973 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14974 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14975 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14976 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14977 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14979 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14980 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14981 gameInfo.result = res;
14983 gameInfo.resultDetails = StrSave(buf);
14985 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14986 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14991 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
14995 ChessProgramState *cps = (ChessProgramState *)closure;
14997 if (isr != cps->isr) return; /* Killed intentionally */
15000 RemoveInputSource(cps->isr);
15001 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15002 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15003 _(cps->which), cps->program);
15004 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15005 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15006 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15007 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15008 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15010 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15011 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15012 gameInfo.result = res;
15014 gameInfo.resultDetails = StrSave(buf);
15016 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15017 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15019 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15020 _(cps->which), cps->program);
15021 RemoveInputSource(cps->isr);
15023 /* [AS] Program is misbehaving badly... kill it */
15024 if( count == -2 ) {
15025 DestroyChildProcess( cps->pr, 9 );
15029 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15034 if ((end_str = strchr(message, '\r')) != NULL)
15035 *end_str = NULLCHAR;
15036 if ((end_str = strchr(message, '\n')) != NULL)
15037 *end_str = NULLCHAR;
15039 if (appData.debugMode) {
15040 TimeMark now; int print = 1;
15041 char *quote = ""; char c; int i;
15043 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15044 char start = message[0];
15045 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15046 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15047 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15048 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15049 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15050 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15051 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15052 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15053 sscanf(message, "hint: %c", &c)!=1 &&
15054 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15055 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15056 print = (appData.engineComments >= 2);
15058 message[0] = start; // restore original message
15062 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15063 SubtractTimeMarks(&now, &programStartTime), cps->which,
15069 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15070 if (appData.icsEngineAnalyze) {
15071 if (strstr(message, "whisper") != NULL ||
15072 strstr(message, "kibitz") != NULL ||
15073 strstr(message, "tellics") != NULL) return;
15076 HandleMachineMove(message, cps);
15081 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15086 if( timeControl_2 > 0 ) {
15087 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15088 tc = timeControl_2;
15091 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15092 inc /= cps->timeOdds;
15093 st /= cps->timeOdds;
15095 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15098 /* Set exact time per move, normally using st command */
15099 if (cps->stKludge) {
15100 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15102 if (seconds == 0) {
15103 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15105 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15108 snprintf(buf, MSG_SIZ, "st %d\n", st);
15111 /* Set conventional or incremental time control, using level command */
15112 if (seconds == 0) {
15113 /* Note old gnuchess bug -- minutes:seconds used to not work.
15114 Fixed in later versions, but still avoid :seconds
15115 when seconds is 0. */
15116 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15118 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15119 seconds, inc/1000.);
15122 SendToProgram(buf, cps);
15124 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15125 /* Orthogonally, limit search to given depth */
15127 if (cps->sdKludge) {
15128 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15130 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15132 SendToProgram(buf, cps);
15135 if(cps->nps >= 0) { /* [HGM] nps */
15136 if(cps->supportsNPS == FALSE)
15137 cps->nps = -1; // don't use if engine explicitly says not supported!
15139 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15140 SendToProgram(buf, cps);
15145 ChessProgramState *
15147 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15149 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15150 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15156 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15158 char message[MSG_SIZ];
15161 /* Note: this routine must be called when the clocks are stopped
15162 or when they have *just* been set or switched; otherwise
15163 it will be off by the time since the current tick started.
15165 if (machineWhite) {
15166 time = whiteTimeRemaining / 10;
15167 otime = blackTimeRemaining / 10;
15169 time = blackTimeRemaining / 10;
15170 otime = whiteTimeRemaining / 10;
15172 /* [HGM] translate opponent's time by time-odds factor */
15173 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15174 if (appData.debugMode) {
15175 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15178 if (time <= 0) time = 1;
15179 if (otime <= 0) otime = 1;
15181 snprintf(message, MSG_SIZ, "time %ld\n", time);
15182 SendToProgram(message, cps);
15184 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15185 SendToProgram(message, cps);
15189 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15192 int len = strlen(name);
15195 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15197 sscanf(*p, "%d", &val);
15199 while (**p && **p != ' ')
15201 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15202 SendToProgram(buf, cps);
15209 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15212 int len = strlen(name);
15213 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15215 sscanf(*p, "%d", loc);
15216 while (**p && **p != ' ') (*p)++;
15217 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15218 SendToProgram(buf, cps);
15225 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15228 int len = strlen(name);
15229 if (strncmp((*p), name, len) == 0
15230 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15232 sscanf(*p, "%[^\"]", loc);
15233 while (**p && **p != '\"') (*p)++;
15234 if (**p == '\"') (*p)++;
15235 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15236 SendToProgram(buf, cps);
15243 ParseOption (Option *opt, ChessProgramState *cps)
15244 // [HGM] options: process the string that defines an engine option, and determine
15245 // name, type, default value, and allowed value range
15247 char *p, *q, buf[MSG_SIZ];
15248 int n, min = (-1)<<31, max = 1<<31, def;
15250 if(p = strstr(opt->name, " -spin ")) {
15251 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15252 if(max < min) max = min; // enforce consistency
15253 if(def < min) def = min;
15254 if(def > max) def = max;
15259 } else if((p = strstr(opt->name, " -slider "))) {
15260 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15261 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15262 if(max < min) max = min; // enforce consistency
15263 if(def < min) def = min;
15264 if(def > max) def = max;
15268 opt->type = Spin; // Slider;
15269 } else if((p = strstr(opt->name, " -string "))) {
15270 opt->textValue = p+9;
15271 opt->type = TextBox;
15272 } else if((p = strstr(opt->name, " -file "))) {
15273 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15274 opt->textValue = p+7;
15275 opt->type = FileName; // FileName;
15276 } else if((p = strstr(opt->name, " -path "))) {
15277 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15278 opt->textValue = p+7;
15279 opt->type = PathName; // PathName;
15280 } else if(p = strstr(opt->name, " -check ")) {
15281 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15282 opt->value = (def != 0);
15283 opt->type = CheckBox;
15284 } else if(p = strstr(opt->name, " -combo ")) {
15285 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15286 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15287 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15288 opt->value = n = 0;
15289 while(q = StrStr(q, " /// ")) {
15290 n++; *q = 0; // count choices, and null-terminate each of them
15292 if(*q == '*') { // remember default, which is marked with * prefix
15296 cps->comboList[cps->comboCnt++] = q;
15298 cps->comboList[cps->comboCnt++] = NULL;
15300 opt->type = ComboBox;
15301 } else if(p = strstr(opt->name, " -button")) {
15302 opt->type = Button;
15303 } else if(p = strstr(opt->name, " -save")) {
15304 opt->type = SaveButton;
15305 } else return FALSE;
15306 *p = 0; // terminate option name
15307 // now look if the command-line options define a setting for this engine option.
15308 if(cps->optionSettings && cps->optionSettings[0])
15309 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15310 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15311 snprintf(buf, MSG_SIZ, "option %s", p);
15312 if(p = strstr(buf, ",")) *p = 0;
15313 if(q = strchr(buf, '=')) switch(opt->type) {
15315 for(n=0; n<opt->max; n++)
15316 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15319 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15323 opt->value = atoi(q+1);
15328 SendToProgram(buf, cps);
15334 FeatureDone (ChessProgramState *cps, int val)
15336 DelayedEventCallback cb = GetDelayedEvent();
15337 if ((cb == InitBackEnd3 && cps == &first) ||
15338 (cb == SettingsMenuIfReady && cps == &second) ||
15339 (cb == LoadEngine) ||
15340 (cb == TwoMachinesEventIfReady)) {
15341 CancelDelayedEvent();
15342 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15344 cps->initDone = val;
15347 /* Parse feature command from engine */
15349 ParseFeatures (char *args, ChessProgramState *cps)
15357 while (*p == ' ') p++;
15358 if (*p == NULLCHAR) return;
15360 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15361 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15362 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15363 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15364 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15365 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15366 if (BoolFeature(&p, "reuse", &val, cps)) {
15367 /* Engine can disable reuse, but can't enable it if user said no */
15368 if (!val) cps->reuse = FALSE;
15371 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15372 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15373 if (gameMode == TwoMachinesPlay) {
15374 DisplayTwoMachinesTitle();
15380 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15381 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15382 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15383 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15384 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15385 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15386 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15387 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15388 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15389 if (IntFeature(&p, "done", &val, cps)) {
15390 FeatureDone(cps, val);
15393 /* Added by Tord: */
15394 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15395 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15396 /* End of additions by Tord */
15398 /* [HGM] added features: */
15399 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15400 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15401 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15402 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15403 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15404 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15405 if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15406 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15407 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15408 SendToProgram(buf, cps);
15411 if(cps->nrOptions >= MAX_OPTIONS) {
15413 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15414 DisplayError(buf, 0);
15418 /* End of additions by HGM */
15420 /* unknown feature: complain and skip */
15422 while (*q && *q != '=') q++;
15423 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15424 SendToProgram(buf, cps);
15430 while (*p && *p != '\"') p++;
15431 if (*p == '\"') p++;
15433 while (*p && *p != ' ') p++;
15441 PeriodicUpdatesEvent (int newState)
15443 if (newState == appData.periodicUpdates)
15446 appData.periodicUpdates=newState;
15448 /* Display type changes, so update it now */
15449 // DisplayAnalysis();
15451 /* Get the ball rolling again... */
15453 AnalysisPeriodicEvent(1);
15454 StartAnalysisClock();
15459 PonderNextMoveEvent (int newState)
15461 if (newState == appData.ponderNextMove) return;
15462 if (gameMode == EditPosition) EditPositionDone(TRUE);
15464 SendToProgram("hard\n", &first);
15465 if (gameMode == TwoMachinesPlay) {
15466 SendToProgram("hard\n", &second);
15469 SendToProgram("easy\n", &first);
15470 thinkOutput[0] = NULLCHAR;
15471 if (gameMode == TwoMachinesPlay) {
15472 SendToProgram("easy\n", &second);
15475 appData.ponderNextMove = newState;
15479 NewSettingEvent (int option, int *feature, char *command, int value)
15483 if (gameMode == EditPosition) EditPositionDone(TRUE);
15484 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15485 if(feature == NULL || *feature) SendToProgram(buf, &first);
15486 if (gameMode == TwoMachinesPlay) {
15487 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15492 ShowThinkingEvent ()
15493 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15495 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15496 int newState = appData.showThinking
15497 // [HGM] thinking: other features now need thinking output as well
15498 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15500 if (oldState == newState) return;
15501 oldState = newState;
15502 if (gameMode == EditPosition) EditPositionDone(TRUE);
15504 SendToProgram("post\n", &first);
15505 if (gameMode == TwoMachinesPlay) {
15506 SendToProgram("post\n", &second);
15509 SendToProgram("nopost\n", &first);
15510 thinkOutput[0] = NULLCHAR;
15511 if (gameMode == TwoMachinesPlay) {
15512 SendToProgram("nopost\n", &second);
15515 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15519 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15521 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15522 if (pr == NoProc) return;
15523 AskQuestion(title, question, replyPrefix, pr);
15527 TypeInEvent (char firstChar)
15529 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15530 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15531 gameMode == AnalyzeMode || gameMode == EditGame ||
15532 gameMode == EditPosition || gameMode == IcsExamining ||
15533 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15534 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15535 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15536 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15537 gameMode == Training) PopUpMoveDialog(firstChar);
15541 TypeInDoneEvent (char *move)
15544 int n, fromX, fromY, toX, toY;
15546 ChessMove moveType;
15549 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15550 EditPositionPasteFEN(move);
15553 // [HGM] movenum: allow move number to be typed in any mode
15554 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15559 if (gameMode != EditGame && currentMove != forwardMostMove &&
15560 gameMode != Training) {
15561 DisplayMoveError(_("Displayed move is not current"));
15563 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15564 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15565 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15566 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15567 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15568 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15570 DisplayMoveError(_("Could not parse move"));
15576 DisplayMove (int moveNumber)
15578 char message[MSG_SIZ];
15580 char cpThinkOutput[MSG_SIZ];
15582 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15584 if (moveNumber == forwardMostMove - 1 ||
15585 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15587 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15589 if (strchr(cpThinkOutput, '\n')) {
15590 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15593 *cpThinkOutput = NULLCHAR;
15596 /* [AS] Hide thinking from human user */
15597 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15598 *cpThinkOutput = NULLCHAR;
15599 if( thinkOutput[0] != NULLCHAR ) {
15602 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15603 cpThinkOutput[i] = '.';
15605 cpThinkOutput[i] = NULLCHAR;
15606 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15610 if (moveNumber == forwardMostMove - 1 &&
15611 gameInfo.resultDetails != NULL) {
15612 if (gameInfo.resultDetails[0] == NULLCHAR) {
15613 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15615 snprintf(res, MSG_SIZ, " {%s} %s",
15616 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15622 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15623 DisplayMessage(res, cpThinkOutput);
15625 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15626 WhiteOnMove(moveNumber) ? " " : ".. ",
15627 parseList[moveNumber], res);
15628 DisplayMessage(message, cpThinkOutput);
15633 DisplayComment (int moveNumber, char *text)
15635 char title[MSG_SIZ];
15637 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15638 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15640 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15641 WhiteOnMove(moveNumber) ? " " : ".. ",
15642 parseList[moveNumber]);
15644 if (text != NULL && (appData.autoDisplayComment || commentUp))
15645 CommentPopUp(title, text);
15648 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15649 * might be busy thinking or pondering. It can be omitted if your
15650 * gnuchess is configured to stop thinking immediately on any user
15651 * input. However, that gnuchess feature depends on the FIONREAD
15652 * ioctl, which does not work properly on some flavors of Unix.
15655 Attention (ChessProgramState *cps)
15658 if (!cps->useSigint) return;
15659 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15660 switch (gameMode) {
15661 case MachinePlaysWhite:
15662 case MachinePlaysBlack:
15663 case TwoMachinesPlay:
15664 case IcsPlayingWhite:
15665 case IcsPlayingBlack:
15668 /* Skip if we know it isn't thinking */
15669 if (!cps->maybeThinking) return;
15670 if (appData.debugMode)
15671 fprintf(debugFP, "Interrupting %s\n", cps->which);
15672 InterruptChildProcess(cps->pr);
15673 cps->maybeThinking = FALSE;
15678 #endif /*ATTENTION*/
15684 if (whiteTimeRemaining <= 0) {
15687 if (appData.icsActive) {
15688 if (appData.autoCallFlag &&
15689 gameMode == IcsPlayingBlack && !blackFlag) {
15690 SendToICS(ics_prefix);
15691 SendToICS("flag\n");
15695 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15697 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15698 if (appData.autoCallFlag) {
15699 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15706 if (blackTimeRemaining <= 0) {
15709 if (appData.icsActive) {
15710 if (appData.autoCallFlag &&
15711 gameMode == IcsPlayingWhite && !whiteFlag) {
15712 SendToICS(ics_prefix);
15713 SendToICS("flag\n");
15717 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15719 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15720 if (appData.autoCallFlag) {
15721 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15732 CheckTimeControl ()
15734 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15735 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15738 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15740 if ( !WhiteOnMove(forwardMostMove) ) {
15741 /* White made time control */
15742 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15743 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15744 /* [HGM] time odds: correct new time quota for time odds! */
15745 / WhitePlayer()->timeOdds;
15746 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15748 lastBlack -= blackTimeRemaining;
15749 /* Black made time control */
15750 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15751 / WhitePlayer()->other->timeOdds;
15752 lastWhite = whiteTimeRemaining;
15757 DisplayBothClocks ()
15759 int wom = gameMode == EditPosition ?
15760 !blackPlaysFirst : WhiteOnMove(currentMove);
15761 DisplayWhiteClock(whiteTimeRemaining, wom);
15762 DisplayBlackClock(blackTimeRemaining, !wom);
15766 /* Timekeeping seems to be a portability nightmare. I think everyone
15767 has ftime(), but I'm really not sure, so I'm including some ifdefs
15768 to use other calls if you don't. Clocks will be less accurate if
15769 you have neither ftime nor gettimeofday.
15772 /* VS 2008 requires the #include outside of the function */
15773 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15774 #include <sys/timeb.h>
15777 /* Get the current time as a TimeMark */
15779 GetTimeMark (TimeMark *tm)
15781 #if HAVE_GETTIMEOFDAY
15783 struct timeval timeVal;
15784 struct timezone timeZone;
15786 gettimeofday(&timeVal, &timeZone);
15787 tm->sec = (long) timeVal.tv_sec;
15788 tm->ms = (int) (timeVal.tv_usec / 1000L);
15790 #else /*!HAVE_GETTIMEOFDAY*/
15793 // include <sys/timeb.h> / moved to just above start of function
15794 struct timeb timeB;
15797 tm->sec = (long) timeB.time;
15798 tm->ms = (int) timeB.millitm;
15800 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15801 tm->sec = (long) time(NULL);
15807 /* Return the difference in milliseconds between two
15808 time marks. We assume the difference will fit in a long!
15811 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15813 return 1000L*(tm2->sec - tm1->sec) +
15814 (long) (tm2->ms - tm1->ms);
15819 * Code to manage the game clocks.
15821 * In tournament play, black starts the clock and then white makes a move.
15822 * We give the human user a slight advantage if he is playing white---the
15823 * clocks don't run until he makes his first move, so it takes zero time.
15824 * Also, we don't account for network lag, so we could get out of sync
15825 * with GNU Chess's clock -- but then, referees are always right.
15828 static TimeMark tickStartTM;
15829 static long intendedTickLength;
15832 NextTickLength (long timeRemaining)
15834 long nominalTickLength, nextTickLength;
15836 if (timeRemaining > 0L && timeRemaining <= 10000L)
15837 nominalTickLength = 100L;
15839 nominalTickLength = 1000L;
15840 nextTickLength = timeRemaining % nominalTickLength;
15841 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15843 return nextTickLength;
15846 /* Adjust clock one minute up or down */
15848 AdjustClock (Boolean which, int dir)
15850 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15851 if(which) blackTimeRemaining += 60000*dir;
15852 else whiteTimeRemaining += 60000*dir;
15853 DisplayBothClocks();
15854 adjustedClock = TRUE;
15857 /* Stop clocks and reset to a fresh time control */
15861 (void) StopClockTimer();
15862 if (appData.icsActive) {
15863 whiteTimeRemaining = blackTimeRemaining = 0;
15864 } else if (searchTime) {
15865 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15866 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15867 } else { /* [HGM] correct new time quote for time odds */
15868 whiteTC = blackTC = fullTimeControlString;
15869 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15870 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15872 if (whiteFlag || blackFlag) {
15874 whiteFlag = blackFlag = FALSE;
15876 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15877 DisplayBothClocks();
15878 adjustedClock = FALSE;
15881 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15883 /* Decrement running clock by amount of time that has passed */
15887 long timeRemaining;
15888 long lastTickLength, fudge;
15891 if (!appData.clockMode) return;
15892 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15896 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15898 /* Fudge if we woke up a little too soon */
15899 fudge = intendedTickLength - lastTickLength;
15900 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15902 if (WhiteOnMove(forwardMostMove)) {
15903 if(whiteNPS >= 0) lastTickLength = 0;
15904 timeRemaining = whiteTimeRemaining -= lastTickLength;
15905 if(timeRemaining < 0 && !appData.icsActive) {
15906 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15907 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15908 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15909 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15912 DisplayWhiteClock(whiteTimeRemaining - fudge,
15913 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15915 if(blackNPS >= 0) lastTickLength = 0;
15916 timeRemaining = blackTimeRemaining -= lastTickLength;
15917 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15918 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15920 blackStartMove = forwardMostMove;
15921 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15924 DisplayBlackClock(blackTimeRemaining - fudge,
15925 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15927 if (CheckFlags()) return;
15930 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15931 StartClockTimer(intendedTickLength);
15933 /* if the time remaining has fallen below the alarm threshold, sound the
15934 * alarm. if the alarm has sounded and (due to a takeback or time control
15935 * with increment) the time remaining has increased to a level above the
15936 * threshold, reset the alarm so it can sound again.
15939 if (appData.icsActive && appData.icsAlarm) {
15941 /* make sure we are dealing with the user's clock */
15942 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15943 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15946 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15947 alarmSounded = FALSE;
15948 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15950 alarmSounded = TRUE;
15956 /* A player has just moved, so stop the previously running
15957 clock and (if in clock mode) start the other one.
15958 We redisplay both clocks in case we're in ICS mode, because
15959 ICS gives us an update to both clocks after every move.
15960 Note that this routine is called *after* forwardMostMove
15961 is updated, so the last fractional tick must be subtracted
15962 from the color that is *not* on move now.
15965 SwitchClocks (int newMoveNr)
15967 long lastTickLength;
15969 int flagged = FALSE;
15973 if (StopClockTimer() && appData.clockMode) {
15974 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15975 if (!WhiteOnMove(forwardMostMove)) {
15976 if(blackNPS >= 0) lastTickLength = 0;
15977 blackTimeRemaining -= lastTickLength;
15978 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15979 // if(pvInfoList[forwardMostMove].time == -1)
15980 pvInfoList[forwardMostMove].time = // use GUI time
15981 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15983 if(whiteNPS >= 0) lastTickLength = 0;
15984 whiteTimeRemaining -= lastTickLength;
15985 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15986 // if(pvInfoList[forwardMostMove].time == -1)
15987 pvInfoList[forwardMostMove].time =
15988 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15990 flagged = CheckFlags();
15992 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15993 CheckTimeControl();
15995 if (flagged || !appData.clockMode) return;
15997 switch (gameMode) {
15998 case MachinePlaysBlack:
15999 case MachinePlaysWhite:
16000 case BeginningOfGame:
16001 if (pausing) return;
16005 case PlayFromGameFile:
16013 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16014 if(WhiteOnMove(forwardMostMove))
16015 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16016 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16020 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16021 whiteTimeRemaining : blackTimeRemaining);
16022 StartClockTimer(intendedTickLength);
16026 /* Stop both clocks */
16030 long lastTickLength;
16033 if (!StopClockTimer()) return;
16034 if (!appData.clockMode) return;
16038 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16039 if (WhiteOnMove(forwardMostMove)) {
16040 if(whiteNPS >= 0) lastTickLength = 0;
16041 whiteTimeRemaining -= lastTickLength;
16042 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16044 if(blackNPS >= 0) lastTickLength = 0;
16045 blackTimeRemaining -= lastTickLength;
16046 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16051 /* Start clock of player on move. Time may have been reset, so
16052 if clock is already running, stop and restart it. */
16056 (void) StopClockTimer(); /* in case it was running already */
16057 DisplayBothClocks();
16058 if (CheckFlags()) return;
16060 if (!appData.clockMode) return;
16061 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16063 GetTimeMark(&tickStartTM);
16064 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16065 whiteTimeRemaining : blackTimeRemaining);
16067 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16068 whiteNPS = blackNPS = -1;
16069 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16070 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16071 whiteNPS = first.nps;
16072 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16073 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16074 blackNPS = first.nps;
16075 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16076 whiteNPS = second.nps;
16077 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16078 blackNPS = second.nps;
16079 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16081 StartClockTimer(intendedTickLength);
16085 TimeString (long ms)
16087 long second, minute, hour, day;
16089 static char buf[32];
16091 if (ms > 0 && ms <= 9900) {
16092 /* convert milliseconds to tenths, rounding up */
16093 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16095 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16099 /* convert milliseconds to seconds, rounding up */
16100 /* use floating point to avoid strangeness of integer division
16101 with negative dividends on many machines */
16102 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16109 day = second / (60 * 60 * 24);
16110 second = second % (60 * 60 * 24);
16111 hour = second / (60 * 60);
16112 second = second % (60 * 60);
16113 minute = second / 60;
16114 second = second % 60;
16117 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16118 sign, day, hour, minute, second);
16120 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16122 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16129 * This is necessary because some C libraries aren't ANSI C compliant yet.
16132 StrStr (char *string, char *match)
16136 length = strlen(match);
16138 for (i = strlen(string) - length; i >= 0; i--, string++)
16139 if (!strncmp(match, string, length))
16146 StrCaseStr (char *string, char *match)
16150 length = strlen(match);
16152 for (i = strlen(string) - length; i >= 0; i--, string++) {
16153 for (j = 0; j < length; j++) {
16154 if (ToLower(match[j]) != ToLower(string[j]))
16157 if (j == length) return string;
16165 StrCaseCmp (char *s1, char *s2)
16170 c1 = ToLower(*s1++);
16171 c2 = ToLower(*s2++);
16172 if (c1 > c2) return 1;
16173 if (c1 < c2) return -1;
16174 if (c1 == NULLCHAR) return 0;
16182 return isupper(c) ? tolower(c) : c;
16189 return islower(c) ? toupper(c) : c;
16191 #endif /* !_amigados */
16198 if ((ret = (char *) malloc(strlen(s) + 1)))
16200 safeStrCpy(ret, s, strlen(s)+1);
16206 StrSavePtr (char *s, char **savePtr)
16211 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16212 safeStrCpy(*savePtr, s, strlen(s)+1);
16224 clock = time((time_t *)NULL);
16225 tm = localtime(&clock);
16226 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16227 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16228 return StrSave(buf);
16233 PositionToFEN (int move, char *overrideCastling)
16235 int i, j, fromX, fromY, toX, toY;
16242 whiteToPlay = (gameMode == EditPosition) ?
16243 !blackPlaysFirst : (move % 2 == 0);
16246 /* Piece placement data */
16247 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16248 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16250 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16251 if (boards[move][i][j] == EmptySquare) {
16253 } else { ChessSquare piece = boards[move][i][j];
16254 if (emptycount > 0) {
16255 if(emptycount<10) /* [HGM] can be >= 10 */
16256 *p++ = '0' + emptycount;
16257 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16260 if(PieceToChar(piece) == '+') {
16261 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16263 piece = (ChessSquare)(DEMOTED piece);
16265 *p++ = PieceToChar(piece);
16267 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16268 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16273 if (emptycount > 0) {
16274 if(emptycount<10) /* [HGM] can be >= 10 */
16275 *p++ = '0' + emptycount;
16276 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16283 /* [HGM] print Crazyhouse or Shogi holdings */
16284 if( gameInfo.holdingsWidth ) {
16285 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16287 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16288 piece = boards[move][i][BOARD_WIDTH-1];
16289 if( piece != EmptySquare )
16290 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16291 *p++ = PieceToChar(piece);
16293 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16294 piece = boards[move][BOARD_HEIGHT-i-1][0];
16295 if( piece != EmptySquare )
16296 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16297 *p++ = PieceToChar(piece);
16300 if( q == p ) *p++ = '-';
16306 *p++ = whiteToPlay ? 'w' : 'b';
16309 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16310 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16312 if(nrCastlingRights) {
16314 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16315 /* [HGM] write directly from rights */
16316 if(boards[move][CASTLING][2] != NoRights &&
16317 boards[move][CASTLING][0] != NoRights )
16318 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16319 if(boards[move][CASTLING][2] != NoRights &&
16320 boards[move][CASTLING][1] != NoRights )
16321 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16322 if(boards[move][CASTLING][5] != NoRights &&
16323 boards[move][CASTLING][3] != NoRights )
16324 *p++ = boards[move][CASTLING][3] + AAA;
16325 if(boards[move][CASTLING][5] != NoRights &&
16326 boards[move][CASTLING][4] != NoRights )
16327 *p++ = boards[move][CASTLING][4] + AAA;
16330 /* [HGM] write true castling rights */
16331 if( nrCastlingRights == 6 ) {
16332 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16333 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16334 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16335 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16336 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16337 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16338 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16339 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16342 if (q == p) *p++ = '-'; /* No castling rights */
16346 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16347 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16348 /* En passant target square */
16349 if (move > backwardMostMove) {
16350 fromX = moveList[move - 1][0] - AAA;
16351 fromY = moveList[move - 1][1] - ONE;
16352 toX = moveList[move - 1][2] - AAA;
16353 toY = moveList[move - 1][3] - ONE;
16354 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16355 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16356 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16358 /* 2-square pawn move just happened */
16360 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16364 } else if(move == backwardMostMove) {
16365 // [HGM] perhaps we should always do it like this, and forget the above?
16366 if((signed char)boards[move][EP_STATUS] >= 0) {
16367 *p++ = boards[move][EP_STATUS] + AAA;
16368 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16379 /* [HGM] find reversible plies */
16380 { int i = 0, j=move;
16382 if (appData.debugMode) { int k;
16383 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16384 for(k=backwardMostMove; k<=forwardMostMove; k++)
16385 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16389 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16390 if( j == backwardMostMove ) i += initialRulePlies;
16391 sprintf(p, "%d ", i);
16392 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16394 /* Fullmove number */
16395 sprintf(p, "%d", (move / 2) + 1);
16397 return StrSave(buf);
16401 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16410 /* [HGM] by default clear Crazyhouse holdings, if present */
16411 if(gameInfo.holdingsWidth) {
16412 for(i=0; i<BOARD_HEIGHT; i++) {
16413 board[i][0] = EmptySquare; /* black holdings */
16414 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16415 board[i][1] = (ChessSquare) 0; /* black counts */
16416 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16420 /* Piece placement data */
16421 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16424 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16425 if (*p == '/') p++;
16426 emptycount = gameInfo.boardWidth - j;
16427 while (emptycount--)
16428 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16430 #if(BOARD_FILES >= 10)
16431 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16432 p++; emptycount=10;
16433 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16434 while (emptycount--)
16435 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16437 } else if (isdigit(*p)) {
16438 emptycount = *p++ - '0';
16439 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16440 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16441 while (emptycount--)
16442 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16443 } else if (*p == '+' || isalpha(*p)) {
16444 if (j >= gameInfo.boardWidth) return FALSE;
16446 piece = CharToPiece(*++p);
16447 if(piece == EmptySquare) return FALSE; /* unknown piece */
16448 piece = (ChessSquare) (PROMOTED piece ); p++;
16449 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16450 } else piece = CharToPiece(*p++);
16452 if(piece==EmptySquare) return FALSE; /* unknown piece */
16453 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16454 piece = (ChessSquare) (PROMOTED piece);
16455 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16458 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16464 while (*p == '/' || *p == ' ') p++;
16466 /* [HGM] look for Crazyhouse holdings here */
16467 while(*p==' ') p++;
16468 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16470 if(*p == '-' ) p++; /* empty holdings */ else {
16471 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16472 /* if we would allow FEN reading to set board size, we would */
16473 /* have to add holdings and shift the board read so far here */
16474 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16476 if((int) piece >= (int) BlackPawn ) {
16477 i = (int)piece - (int)BlackPawn;
16478 i = PieceToNumber((ChessSquare)i);
16479 if( i >= gameInfo.holdingsSize ) return FALSE;
16480 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16481 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16483 i = (int)piece - (int)WhitePawn;
16484 i = PieceToNumber((ChessSquare)i);
16485 if( i >= gameInfo.holdingsSize ) return FALSE;
16486 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16487 board[i][BOARD_WIDTH-2]++; /* black holdings */
16494 while(*p == ' ') p++;
16498 if(appData.colorNickNames) {
16499 if( c == appData.colorNickNames[0] ) c = 'w'; else
16500 if( c == appData.colorNickNames[1] ) c = 'b';
16504 *blackPlaysFirst = FALSE;
16507 *blackPlaysFirst = TRUE;
16513 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16514 /* return the extra info in global variiables */
16516 /* set defaults in case FEN is incomplete */
16517 board[EP_STATUS] = EP_UNKNOWN;
16518 for(i=0; i<nrCastlingRights; i++ ) {
16519 board[CASTLING][i] =
16520 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16521 } /* assume possible unless obviously impossible */
16522 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16523 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16524 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16525 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16526 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16527 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16528 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16529 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16532 while(*p==' ') p++;
16533 if(nrCastlingRights) {
16534 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16535 /* castling indicator present, so default becomes no castlings */
16536 for(i=0; i<nrCastlingRights; i++ ) {
16537 board[CASTLING][i] = NoRights;
16540 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16541 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16542 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16543 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16544 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16547 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16548 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16550 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16551 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16552 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16553 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16554 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16555 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16558 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16559 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16560 board[CASTLING][2] = whiteKingFile;
16563 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16564 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16565 board[CASTLING][2] = whiteKingFile;
16568 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16569 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16570 board[CASTLING][5] = blackKingFile;
16573 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16574 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16575 board[CASTLING][5] = blackKingFile;
16578 default: /* FRC castlings */
16579 if(c >= 'a') { /* black rights */
16580 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16581 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16582 if(i == BOARD_RGHT) break;
16583 board[CASTLING][5] = i;
16585 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16586 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16588 board[CASTLING][3] = c;
16590 board[CASTLING][4] = c;
16591 } else { /* white rights */
16592 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16593 if(board[0][i] == WhiteKing) break;
16594 if(i == BOARD_RGHT) break;
16595 board[CASTLING][2] = i;
16596 c -= AAA - 'a' + 'A';
16597 if(board[0][c] >= WhiteKing) break;
16599 board[CASTLING][0] = c;
16601 board[CASTLING][1] = c;
16605 for(i=0; i<nrCastlingRights; i++)
16606 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16607 if (appData.debugMode) {
16608 fprintf(debugFP, "FEN castling rights:");
16609 for(i=0; i<nrCastlingRights; i++)
16610 fprintf(debugFP, " %d", board[CASTLING][i]);
16611 fprintf(debugFP, "\n");
16614 while(*p==' ') p++;
16617 /* read e.p. field in games that know e.p. capture */
16618 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16619 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16621 p++; board[EP_STATUS] = EP_NONE;
16623 char c = *p++ - AAA;
16625 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16626 if(*p >= '0' && *p <='9') p++;
16627 board[EP_STATUS] = c;
16632 if(sscanf(p, "%d", &i) == 1) {
16633 FENrulePlies = i; /* 50-move ply counter */
16634 /* (The move number is still ignored) */
16641 EditPositionPasteFEN (char *fen)
16644 Board initial_position;
16646 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16647 DisplayError(_("Bad FEN position in clipboard"), 0);
16650 int savedBlackPlaysFirst = blackPlaysFirst;
16651 EditPositionEvent();
16652 blackPlaysFirst = savedBlackPlaysFirst;
16653 CopyBoard(boards[0], initial_position);
16654 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16655 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16656 DisplayBothClocks();
16657 DrawPosition(FALSE, boards[currentMove]);
16662 static char cseq[12] = "\\ ";
16665 set_cont_sequence (char *new_seq)
16670 // handle bad attempts to set the sequence
16672 return 0; // acceptable error - no debug
16674 len = strlen(new_seq);
16675 ret = (len > 0) && (len < sizeof(cseq));
16677 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16678 else if (appData.debugMode)
16679 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16684 reformat a source message so words don't cross the width boundary. internal
16685 newlines are not removed. returns the wrapped size (no null character unless
16686 included in source message). If dest is NULL, only calculate the size required
16687 for the dest buffer. lp argument indicats line position upon entry, and it's
16688 passed back upon exit.
16691 wrap (char *dest, char *src, int count, int width, int *lp)
16693 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16695 cseq_len = strlen(cseq);
16696 old_line = line = *lp;
16697 ansi = len = clen = 0;
16699 for (i=0; i < count; i++)
16701 if (src[i] == '\033')
16704 // if we hit the width, back up
16705 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16707 // store i & len in case the word is too long
16708 old_i = i, old_len = len;
16710 // find the end of the last word
16711 while (i && src[i] != ' ' && src[i] != '\n')
16717 // word too long? restore i & len before splitting it
16718 if ((old_i-i+clen) >= width)
16725 if (i && src[i-1] == ' ')
16728 if (src[i] != ' ' && src[i] != '\n')
16735 // now append the newline and continuation sequence
16740 strncpy(dest+len, cseq, cseq_len);
16748 dest[len] = src[i];
16752 if (src[i] == '\n')
16757 if (dest && appData.debugMode)
16759 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16760 count, width, line, len, *lp);
16761 show_bytes(debugFP, src, count);
16762 fprintf(debugFP, "\ndest: ");
16763 show_bytes(debugFP, dest, len);
16764 fprintf(debugFP, "\n");
16766 *lp = dest ? line : old_line;
16771 // [HGM] vari: routines for shelving variations
16772 Boolean modeRestore = FALSE;
16775 PushInner (int firstMove, int lastMove)
16777 int i, j, nrMoves = lastMove - firstMove;
16779 // push current tail of game on stack
16780 savedResult[storedGames] = gameInfo.result;
16781 savedDetails[storedGames] = gameInfo.resultDetails;
16782 gameInfo.resultDetails = NULL;
16783 savedFirst[storedGames] = firstMove;
16784 savedLast [storedGames] = lastMove;
16785 savedFramePtr[storedGames] = framePtr;
16786 framePtr -= nrMoves; // reserve space for the boards
16787 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16788 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16789 for(j=0; j<MOVE_LEN; j++)
16790 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16791 for(j=0; j<2*MOVE_LEN; j++)
16792 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16793 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16794 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16795 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16796 pvInfoList[firstMove+i-1].depth = 0;
16797 commentList[framePtr+i] = commentList[firstMove+i];
16798 commentList[firstMove+i] = NULL;
16802 forwardMostMove = firstMove; // truncate game so we can start variation
16806 PushTail (int firstMove, int lastMove)
16808 if(appData.icsActive) { // only in local mode
16809 forwardMostMove = currentMove; // mimic old ICS behavior
16812 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16814 PushInner(firstMove, lastMove);
16815 if(storedGames == 1) GreyRevert(FALSE);
16816 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16820 PopInner (Boolean annotate)
16823 char buf[8000], moveBuf[20];
16825 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16826 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16827 nrMoves = savedLast[storedGames] - currentMove;
16830 if(!WhiteOnMove(currentMove))
16831 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16832 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16833 for(i=currentMove; i<forwardMostMove; i++) {
16835 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16836 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16837 strcat(buf, moveBuf);
16838 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16839 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16843 for(i=1; i<=nrMoves; i++) { // copy last variation back
16844 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16845 for(j=0; j<MOVE_LEN; j++)
16846 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16847 for(j=0; j<2*MOVE_LEN; j++)
16848 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16849 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16850 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16851 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16852 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16853 commentList[currentMove+i] = commentList[framePtr+i];
16854 commentList[framePtr+i] = NULL;
16856 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16857 framePtr = savedFramePtr[storedGames];
16858 gameInfo.result = savedResult[storedGames];
16859 if(gameInfo.resultDetails != NULL) {
16860 free(gameInfo.resultDetails);
16862 gameInfo.resultDetails = savedDetails[storedGames];
16863 forwardMostMove = currentMove + nrMoves;
16867 PopTail (Boolean annotate)
16869 if(appData.icsActive) return FALSE; // only in local mode
16870 if(!storedGames) return FALSE; // sanity
16871 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16873 PopInner(annotate);
16874 if(currentMove < forwardMostMove) ForwardEvent(); else
16875 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16877 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16883 { // remove all shelved variations
16885 for(i=0; i<storedGames; i++) {
16886 if(savedDetails[i])
16887 free(savedDetails[i]);
16888 savedDetails[i] = NULL;
16890 for(i=framePtr; i<MAX_MOVES; i++) {
16891 if(commentList[i]) free(commentList[i]);
16892 commentList[i] = NULL;
16894 framePtr = MAX_MOVES-1;
16899 LoadVariation (int index, char *text)
16900 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16901 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16902 int level = 0, move;
16904 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16905 // first find outermost bracketing variation
16906 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16907 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16908 if(*p == '{') wait = '}'; else
16909 if(*p == '[') wait = ']'; else
16910 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16911 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16913 if(*p == wait) wait = NULLCHAR; // closing ]} found
16916 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16917 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16918 end[1] = NULLCHAR; // clip off comment beyond variation
16919 ToNrEvent(currentMove-1);
16920 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16921 // kludge: use ParsePV() to append variation to game
16922 move = currentMove;
16923 ParsePV(start, TRUE, TRUE);
16924 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16925 ClearPremoveHighlights();
16927 ToNrEvent(currentMove+1);