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 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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
260 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
283 /* States for ics_getting_history */
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
291 /* whosays values for GameEnds */
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
303 /* Different types of move when calling RegisterMove */
305 #define CMAIL_RESIGN 1
307 #define CMAIL_ACCEPT 3
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
314 /* Telnet protocol constants */
325 safeStrCpy( char *dst, const char *src, size_t count )
328 assert( dst != NULL );
329 assert( src != NULL );
332 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333 if( i == count && dst[count-1] != NULLCHAR)
335 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336 if(appData.debugMode)
337 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
343 /* Some compiler can't cast u64 to double
344 * This function do the job for us:
346 * We use the highest bit for cast, this only
347 * works if the highest bit is not
348 * in use (This should not happen)
350 * We used this for all compiler
353 u64ToDouble(u64 value)
356 u64 tmp = value & u64Const(0x7fffffffffffffff);
357 r = (double)(s64)tmp;
358 if (value & u64Const(0x8000000000000000))
359 r += 9.2233720368547758080e18; /* 2^63 */
363 /* Fake up flags for now, as we aren't keeping track of castling
364 availability yet. [HGM] Change of logic: the flag now only
365 indicates the type of castlings allowed by the rule of the game.
366 The actual rights themselves are maintained in the array
367 castlingRights, as part of the game history, and are not probed
373 int flags = F_ALL_CASTLE_OK;
374 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375 switch (gameInfo.variant) {
377 flags &= ~F_ALL_CASTLE_OK;
378 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379 flags |= F_IGNORE_CHECK;
381 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386 case VariantKriegspiel:
387 flags |= F_KRIEGSPIEL_CAPTURE;
389 case VariantCapaRandom:
390 case VariantFischeRandom:
391 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392 case VariantNoCastle:
393 case VariantShatranj:
396 flags &= ~F_ALL_CASTLE_OK;
404 FILE *gameFileFP, *debugFP;
407 [AS] Note: sometimes, the sscanf() function is used to parse the input
408 into a fixed-size buffer. Because of this, we must be prepared to
409 receive strings as long as the size of the input buffer, which is currently
410 set to 4K for Windows and 8K for the rest.
411 So, we must either allocate sufficiently large buffers here, or
412 reduce the size of the input buffer in the input reading part.
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
419 ChessProgramState first, second;
421 /* premove variables */
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
462 int have_sent_ICS_logon = 0;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void CleanupTail P((void));
511 ChessSquare FIDEArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515 BlackKing, BlackBishop, BlackKnight, BlackRook }
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522 BlackKing, BlackKing, BlackKnight, BlackRook }
525 ChessSquare KnightmateArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528 { BlackRook, BlackMan, BlackBishop, BlackQueen,
529 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackMan, BlackFerz,
557 BlackKing, BlackMan, BlackKnight, BlackRook }
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 #define GothicArray CapablancaArray
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
616 #define FalconArray CapablancaArray
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
638 Board initialPosition;
641 /* Convert str to a rating. Checks for special cases of "----",
643 "++++", etc. Also strips ()'s */
645 string_to_rating(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;
796 cps->supportsNPS = UNKNOWN;
799 cps->optionSettings = appData.engOptions[n];
801 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802 cps->isUCI = appData.isUCI[n]; /* [AS] */
803 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
805 if (appData.protocolVersion[n] > PROTOVER
806 || appData.protocolVersion[n] < 1)
811 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812 appData.protocolVersion[n]);
813 if( (len > MSG_SIZ) && appData.debugMode )
814 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
816 DisplayFatalError(buf, 0, 2);
820 cps->protocolVersion = appData.protocolVersion[n];
823 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
826 ChessProgramState *savCps;
832 if(WaitForEngine(savCps, LoadEngine)) return;
833 CommonEngineInit(); // recalculate time odds
834 if(gameInfo.variant != StringToVariant(appData.variant)) {
835 // we changed variant when loading the engine; this forces us to reset
836 Reset(TRUE, savCps != &first);
837 EditGameEvent(); // for consistency with other path, as Reset changes mode
839 InitChessProgram(savCps, FALSE);
840 SendToProgram("force\n", savCps);
841 DisplayMessage("", "");
842 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849 ReplaceEngine(ChessProgramState *cps, int n)
853 appData.noChessProgram = FALSE;
854 appData.clockMode = TRUE;
856 if(n) return; // only startup first engine immediately; second can wait
857 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
865 Load(ChessProgramState *cps, int i)
867 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
868 if(engineLine[0]) { // an engine was selected from the combo box
869 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
870 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
871 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
872 ParseArgsFromString(buf);
874 ReplaceEngine(cps, i);
878 while(q = strchr(p, SLASH)) p = q+1;
879 if(*p== NULLCHAR) { DisplayError(_("You did not give an engine executable"), 0); return; }
880 if(engineDir[0] != NULLCHAR)
881 appData.directory[i] = engineDir;
882 else if(p != engineName) { // derive directory from engine path, when not given
884 appData.directory[i] = strdup(engineName);
886 } else appData.directory[i] = ".";
888 snprintf(command, MSG_SIZ, "%s %s", p, params);
891 appData.chessProgram[i] = strdup(p);
892 appData.isUCI[i] = isUCI;
893 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
894 appData.hasOwnBookUCI[i] = hasBook;
897 q = firstChessProgramNames;
898 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
899 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
900 v1 ? " -firstProtocolVersion 1" : "",
901 hasBook ? "" : " -fNoOwnBookUCI",
902 isUCI ? " -fUCI" : "",
903 storeVariant ? " -variant " : "",
904 storeVariant ? VariantName(gameInfo.variant) : "");
905 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
906 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
909 ReplaceEngine(cps, i);
915 int matched, min, sec;
917 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
918 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
920 GetTimeMark(&programStartTime);
921 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
922 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
925 programStats.ok_to_send = 1;
926 programStats.seen_stat = 0;
929 * Initialize game list
935 * Internet chess server status
937 if (appData.icsActive) {
938 appData.matchMode = FALSE;
939 appData.matchGames = 0;
941 appData.noChessProgram = !appData.zippyPlay;
943 appData.zippyPlay = FALSE;
944 appData.zippyTalk = FALSE;
945 appData.noChessProgram = TRUE;
947 if (*appData.icsHelper != NULLCHAR) {
948 appData.useTelnet = TRUE;
949 appData.telnetProgram = appData.icsHelper;
952 appData.zippyTalk = appData.zippyPlay = FALSE;
955 /* [AS] Initialize pv info list [HGM] and game state */
959 for( i=0; i<=framePtr; i++ ) {
960 pvInfoList[i].depth = -1;
961 boards[i][EP_STATUS] = EP_NONE;
962 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
967 * Parse timeControl resource
969 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
970 appData.movesPerSession)) {
972 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
973 DisplayFatalError(buf, 0, 2);
977 * Parse searchTime resource
979 if (*appData.searchTime != NULLCHAR) {
980 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
982 searchTime = min * 60;
983 } else if (matched == 2) {
984 searchTime = min * 60 + sec;
987 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
988 DisplayFatalError(buf, 0, 2);
992 /* [AS] Adjudication threshold */
993 adjudicateLossThreshold = appData.adjudicateLossThreshold;
995 InitEngine(&first, 0);
996 InitEngine(&second, 1);
999 if (appData.icsActive) {
1000 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1001 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1002 appData.clockMode = FALSE;
1003 first.sendTime = second.sendTime = 0;
1007 /* Override some settings from environment variables, for backward
1008 compatibility. Unfortunately it's not feasible to have the env
1009 vars just set defaults, at least in xboard. Ugh.
1011 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1016 if (!appData.icsActive) {
1020 /* Check for variants that are supported only in ICS mode,
1021 or not at all. Some that are accepted here nevertheless
1022 have bugs; see comments below.
1024 VariantClass variant = StringToVariant(appData.variant);
1026 case VariantBughouse: /* need four players and two boards */
1027 case VariantKriegspiel: /* need to hide pieces and move details */
1028 /* case VariantFischeRandom: (Fabien: moved below) */
1029 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1030 if( (len > MSG_SIZ) && appData.debugMode )
1031 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1033 DisplayFatalError(buf, 0, 2);
1036 case VariantUnknown:
1037 case VariantLoadable:
1047 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1048 if( (len > MSG_SIZ) && appData.debugMode )
1049 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1051 DisplayFatalError(buf, 0, 2);
1054 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1055 case VariantFairy: /* [HGM] TestLegality definitely off! */
1056 case VariantGothic: /* [HGM] should work */
1057 case VariantCapablanca: /* [HGM] should work */
1058 case VariantCourier: /* [HGM] initial forced moves not implemented */
1059 case VariantShogi: /* [HGM] could still mate with pawn drop */
1060 case VariantKnightmate: /* [HGM] should work */
1061 case VariantCylinder: /* [HGM] untested */
1062 case VariantFalcon: /* [HGM] untested */
1063 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1064 offboard interposition not understood */
1065 case VariantNormal: /* definitely works! */
1066 case VariantWildCastle: /* pieces not automatically shuffled */
1067 case VariantNoCastle: /* pieces not automatically shuffled */
1068 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1069 case VariantLosers: /* should work except for win condition,
1070 and doesn't know captures are mandatory */
1071 case VariantSuicide: /* should work except for win condition,
1072 and doesn't know captures are mandatory */
1073 case VariantGiveaway: /* should work except for win condition,
1074 and doesn't know captures are mandatory */
1075 case VariantTwoKings: /* should work */
1076 case VariantAtomic: /* should work except for win condition */
1077 case Variant3Check: /* should work except for win condition */
1078 case VariantShatranj: /* should work except for all win conditions */
1079 case VariantMakruk: /* should work except for daw countdown */
1080 case VariantBerolina: /* might work if TestLegality is off */
1081 case VariantCapaRandom: /* should work */
1082 case VariantJanus: /* should work */
1083 case VariantSuper: /* experimental */
1084 case VariantGreat: /* experimental, requires legality testing to be off */
1085 case VariantSChess: /* S-Chess, should work */
1086 case VariantSpartan: /* should work */
1093 int NextIntegerFromString( char ** str, long * value )
1098 while( *s == ' ' || *s == '\t' ) {
1104 if( *s >= '0' && *s <= '9' ) {
1105 while( *s >= '0' && *s <= '9' ) {
1106 *value = *value * 10 + (*s - '0');
1118 int NextTimeControlFromString( char ** str, long * value )
1121 int result = NextIntegerFromString( str, &temp );
1124 *value = temp * 60; /* Minutes */
1125 if( **str == ':' ) {
1127 result = NextIntegerFromString( str, &temp );
1128 *value += temp; /* Seconds */
1135 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1136 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1137 int result = -1, type = 0; long temp, temp2;
1139 if(**str != ':') return -1; // old params remain in force!
1141 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1142 if( NextIntegerFromString( str, &temp ) ) return -1;
1143 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1146 /* time only: incremental or sudden-death time control */
1147 if(**str == '+') { /* increment follows; read it */
1149 if(**str == '!') type = *(*str)++; // Bronstein TC
1150 if(result = NextIntegerFromString( str, &temp2)) return -1;
1151 *inc = temp2 * 1000;
1152 if(**str == '.') { // read fraction of increment
1153 char *start = ++(*str);
1154 if(result = NextIntegerFromString( str, &temp2)) return -1;
1156 while(start++ < *str) temp2 /= 10;
1160 *moves = 0; *tc = temp * 1000; *incType = type;
1164 (*str)++; /* classical time control */
1165 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1176 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1177 { /* [HGM] get time to add from the multi-session time-control string */
1178 int incType, moves=1; /* kludge to force reading of first session */
1179 long time, increment;
1182 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1183 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1185 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1186 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1187 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1188 if(movenr == -1) return time; /* last move before new session */
1189 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1190 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1191 if(!moves) return increment; /* current session is incremental */
1192 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1193 } while(movenr >= -1); /* try again for next session */
1195 return 0; // no new time quota on this move
1199 ParseTimeControl(tc, ti, mps)
1206 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1209 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1210 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1211 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1215 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1217 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1220 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1222 snprintf(buf, MSG_SIZ, ":%s", mytc);
1224 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1226 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1231 /* Parse second time control */
1234 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1242 timeControl_2 = tc2 * 1000;
1252 timeControl = tc1 * 1000;
1255 timeIncrement = ti * 1000; /* convert to ms */
1256 movesPerSession = 0;
1259 movesPerSession = mps;
1267 if (appData.debugMode) {
1268 fprintf(debugFP, "%s\n", programVersion);
1271 set_cont_sequence(appData.wrapContSeq);
1272 if (appData.matchGames > 0) {
1273 appData.matchMode = TRUE;
1274 } else if (appData.matchMode) {
1275 appData.matchGames = 1;
1277 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1278 appData.matchGames = appData.sameColorGames;
1279 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1280 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1281 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1284 if (appData.noChessProgram || first.protocolVersion == 1) {
1287 /* kludge: allow timeout for initial "feature" commands */
1289 DisplayMessage("", _("Starting chess program"));
1290 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1295 CalculateIndex(int index, int gameNr)
1296 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1298 if(index > 0) return index; // fixed nmber
1299 if(index == 0) return 1;
1300 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1301 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1306 LoadGameOrPosition(int gameNr)
1307 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1308 if (*appData.loadGameFile != NULLCHAR) {
1309 if (!LoadGameFromFile(appData.loadGameFile,
1310 CalculateIndex(appData.loadGameIndex, gameNr),
1311 appData.loadGameFile, FALSE)) {
1312 DisplayFatalError(_("Bad game file"), 0, 1);
1315 } else if (*appData.loadPositionFile != NULLCHAR) {
1316 if (!LoadPositionFromFile(appData.loadPositionFile,
1317 CalculateIndex(appData.loadPositionIndex, gameNr),
1318 appData.loadPositionFile)) {
1319 DisplayFatalError(_("Bad position file"), 0, 1);
1327 ReserveGame(int gameNr, char resChar)
1329 FILE *tf = fopen(appData.tourneyFile, "r+");
1330 char *p, *q, c, buf[MSG_SIZ];
1331 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1332 safeStrCpy(buf, lastMsg, MSG_SIZ);
1333 DisplayMessage(_("Pick new game"), "");
1334 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1335 ParseArgsFromFile(tf);
1336 p = q = appData.results;
1337 if(appData.debugMode) {
1338 char *r = appData.participants;
1339 fprintf(debugFP, "results = '%s'\n", p);
1340 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1341 fprintf(debugFP, "\n");
1343 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1345 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1346 safeStrCpy(q, p, strlen(p) + 2);
1347 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1348 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1349 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1350 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1353 fseek(tf, -(strlen(p)+4), SEEK_END);
1355 if(c != '"') // depending on DOS or Unix line endings we can be one off
1356 fseek(tf, -(strlen(p)+2), SEEK_END);
1357 else fseek(tf, -(strlen(p)+3), SEEK_END);
1358 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1359 DisplayMessage(buf, "");
1360 free(p); appData.results = q;
1361 if(nextGame <= appData.matchGames && resChar != ' ' &&
1362 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1363 UnloadEngine(&first); // next game belongs to other pairing;
1364 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1369 MatchEvent(int mode)
1370 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1372 if(matchMode) { // already in match mode: switch it off
1373 appData.matchGames = matchGame; // kludge to let match terminate after next game.
1374 ModeHighlight(); // kludgey way to remove checkmark...
1377 if(gameMode != BeginningOfGame) {
1378 DisplayError(_("You can only start a match from the initial position."), 0);
1381 appData.matchGames = appData.defaultMatchGames;
1382 /* Set up machine vs. machine match */
1384 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1385 if(appData.tourneyFile[0]) {
1387 if(nextGame > appData.matchGames) {
1389 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1390 DisplayError(buf, 0);
1391 appData.tourneyFile[0] = 0;
1395 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1396 DisplayFatalError(_("Can't have a match with no chess programs"),
1401 matchGame = roundNr = 1;
1402 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1407 InitBackEnd3 P((void))
1409 GameMode initialMode;
1413 InitChessProgram(&first, startedFromSetupPosition);
1415 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1416 free(programVersion);
1417 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1418 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1421 if (appData.icsActive) {
1423 /* [DM] Make a console window if needed [HGM] merged ifs */
1429 if (*appData.icsCommPort != NULLCHAR)
1430 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1431 appData.icsCommPort);
1433 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1434 appData.icsHost, appData.icsPort);
1436 if( (len > MSG_SIZ) && appData.debugMode )
1437 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1439 DisplayFatalError(buf, err, 1);
1444 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1446 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1447 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1448 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1449 } else if (appData.noChessProgram) {
1455 if (*appData.cmailGameName != NULLCHAR) {
1457 OpenLoopback(&cmailPR);
1459 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1463 DisplayMessage("", "");
1464 if (StrCaseCmp(appData.initialMode, "") == 0) {
1465 initialMode = BeginningOfGame;
1466 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1467 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1468 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1469 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1472 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1473 initialMode = TwoMachinesPlay;
1474 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1475 initialMode = AnalyzeFile;
1476 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1477 initialMode = AnalyzeMode;
1478 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1479 initialMode = MachinePlaysWhite;
1480 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1481 initialMode = MachinePlaysBlack;
1482 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1483 initialMode = EditGame;
1484 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1485 initialMode = EditPosition;
1486 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1487 initialMode = Training;
1489 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1490 if( (len > MSG_SIZ) && appData.debugMode )
1491 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1493 DisplayFatalError(buf, 0, 2);
1497 if (appData.matchMode) {
1498 if(appData.tourneyFile[0]) { // start tourney from command line
1500 if(f = fopen(appData.tourneyFile, "r")) {
1501 ParseArgsFromFile(f); // make sure tourney parmeters re known
1503 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1506 } else if (*appData.cmailGameName != NULLCHAR) {
1507 /* Set up cmail mode */
1508 ReloadCmailMsgEvent(TRUE);
1510 /* Set up other modes */
1511 if (initialMode == AnalyzeFile) {
1512 if (*appData.loadGameFile == NULLCHAR) {
1513 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1517 if (*appData.loadGameFile != NULLCHAR) {
1518 (void) LoadGameFromFile(appData.loadGameFile,
1519 appData.loadGameIndex,
1520 appData.loadGameFile, TRUE);
1521 } else if (*appData.loadPositionFile != NULLCHAR) {
1522 (void) LoadPositionFromFile(appData.loadPositionFile,
1523 appData.loadPositionIndex,
1524 appData.loadPositionFile);
1525 /* [HGM] try to make self-starting even after FEN load */
1526 /* to allow automatic setup of fairy variants with wtm */
1527 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1528 gameMode = BeginningOfGame;
1529 setboardSpoiledMachineBlack = 1;
1531 /* [HGM] loadPos: make that every new game uses the setup */
1532 /* from file as long as we do not switch variant */
1533 if(!blackPlaysFirst) {
1534 startedFromPositionFile = TRUE;
1535 CopyBoard(filePosition, boards[0]);
1538 if (initialMode == AnalyzeMode) {
1539 if (appData.noChessProgram) {
1540 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1543 if (appData.icsActive) {
1544 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1548 } else if (initialMode == AnalyzeFile) {
1549 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1550 ShowThinkingEvent();
1552 AnalysisPeriodicEvent(1);
1553 } else if (initialMode == MachinePlaysWhite) {
1554 if (appData.noChessProgram) {
1555 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1559 if (appData.icsActive) {
1560 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1564 MachineWhiteEvent();
1565 } else if (initialMode == MachinePlaysBlack) {
1566 if (appData.noChessProgram) {
1567 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1571 if (appData.icsActive) {
1572 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1576 MachineBlackEvent();
1577 } else if (initialMode == TwoMachinesPlay) {
1578 if (appData.noChessProgram) {
1579 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1583 if (appData.icsActive) {
1584 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1589 } else if (initialMode == EditGame) {
1591 } else if (initialMode == EditPosition) {
1592 EditPositionEvent();
1593 } else if (initialMode == Training) {
1594 if (*appData.loadGameFile == NULLCHAR) {
1595 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1604 * Establish will establish a contact to a remote host.port.
1605 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1606 * used to talk to the host.
1607 * Returns 0 if okay, error code if not.
1614 if (*appData.icsCommPort != NULLCHAR) {
1615 /* Talk to the host through a serial comm port */
1616 return OpenCommPort(appData.icsCommPort, &icsPR);
1618 } else if (*appData.gateway != NULLCHAR) {
1619 if (*appData.remoteShell == NULLCHAR) {
1620 /* Use the rcmd protocol to run telnet program on a gateway host */
1621 snprintf(buf, sizeof(buf), "%s %s %s",
1622 appData.telnetProgram, appData.icsHost, appData.icsPort);
1623 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1626 /* Use the rsh program to run telnet program on a gateway host */
1627 if (*appData.remoteUser == NULLCHAR) {
1628 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1629 appData.gateway, appData.telnetProgram,
1630 appData.icsHost, appData.icsPort);
1632 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1633 appData.remoteShell, appData.gateway,
1634 appData.remoteUser, appData.telnetProgram,
1635 appData.icsHost, appData.icsPort);
1637 return StartChildProcess(buf, "", &icsPR);
1640 } else if (appData.useTelnet) {
1641 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1644 /* TCP socket interface differs somewhat between
1645 Unix and NT; handle details in the front end.
1647 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1651 void EscapeExpand(char *p, char *q)
1652 { // [HGM] initstring: routine to shape up string arguments
1653 while(*p++ = *q++) if(p[-1] == '\\')
1655 case 'n': p[-1] = '\n'; break;
1656 case 'r': p[-1] = '\r'; break;
1657 case 't': p[-1] = '\t'; break;
1658 case '\\': p[-1] = '\\'; break;
1659 case 0: *p = 0; return;
1660 default: p[-1] = q[-1]; break;
1665 show_bytes(fp, buf, count)
1671 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1672 fprintf(fp, "\\%03o", *buf & 0xff);
1681 /* Returns an errno value */
1683 OutputMaybeTelnet(pr, message, count, outError)
1689 char buf[8192], *p, *q, *buflim;
1690 int left, newcount, outcount;
1692 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1693 *appData.gateway != NULLCHAR) {
1694 if (appData.debugMode) {
1695 fprintf(debugFP, ">ICS: ");
1696 show_bytes(debugFP, message, count);
1697 fprintf(debugFP, "\n");
1699 return OutputToProcess(pr, message, count, outError);
1702 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1709 if (appData.debugMode) {
1710 fprintf(debugFP, ">ICS: ");
1711 show_bytes(debugFP, buf, newcount);
1712 fprintf(debugFP, "\n");
1714 outcount = OutputToProcess(pr, buf, newcount, outError);
1715 if (outcount < newcount) return -1; /* to be sure */
1722 } else if (((unsigned char) *p) == TN_IAC) {
1723 *q++ = (char) TN_IAC;
1730 if (appData.debugMode) {
1731 fprintf(debugFP, ">ICS: ");
1732 show_bytes(debugFP, buf, newcount);
1733 fprintf(debugFP, "\n");
1735 outcount = OutputToProcess(pr, buf, newcount, outError);
1736 if (outcount < newcount) return -1; /* to be sure */
1741 read_from_player(isr, closure, message, count, error)
1748 int outError, outCount;
1749 static int gotEof = 0;
1751 /* Pass data read from player on to ICS */
1754 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1755 if (outCount < count) {
1756 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1758 } else if (count < 0) {
1759 RemoveInputSource(isr);
1760 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1761 } else if (gotEof++ > 0) {
1762 RemoveInputSource(isr);
1763 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1769 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1770 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1771 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1772 SendToICS("date\n");
1773 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1776 /* added routine for printf style output to ics */
1777 void ics_printf(char *format, ...)
1779 char buffer[MSG_SIZ];
1782 va_start(args, format);
1783 vsnprintf(buffer, sizeof(buffer), format, args);
1784 buffer[sizeof(buffer)-1] = '\0';
1793 int count, outCount, outError;
1795 if (icsPR == NULL) return;
1798 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1799 if (outCount < count) {
1800 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1804 /* This is used for sending logon scripts to the ICS. Sending
1805 without a delay causes problems when using timestamp on ICC
1806 (at least on my machine). */
1808 SendToICSDelayed(s,msdelay)
1812 int count, outCount, outError;
1814 if (icsPR == NULL) return;
1817 if (appData.debugMode) {
1818 fprintf(debugFP, ">ICS: ");
1819 show_bytes(debugFP, s, count);
1820 fprintf(debugFP, "\n");
1822 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1824 if (outCount < count) {
1825 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1830 /* Remove all highlighting escape sequences in s
1831 Also deletes any suffix starting with '('
1834 StripHighlightAndTitle(s)
1837 static char retbuf[MSG_SIZ];
1840 while (*s != NULLCHAR) {
1841 while (*s == '\033') {
1842 while (*s != NULLCHAR && !isalpha(*s)) s++;
1843 if (*s != NULLCHAR) s++;
1845 while (*s != NULLCHAR && *s != '\033') {
1846 if (*s == '(' || *s == '[') {
1857 /* Remove all highlighting escape sequences in s */
1862 static char retbuf[MSG_SIZ];
1865 while (*s != NULLCHAR) {
1866 while (*s == '\033') {
1867 while (*s != NULLCHAR && !isalpha(*s)) s++;
1868 if (*s != NULLCHAR) s++;
1870 while (*s != NULLCHAR && *s != '\033') {
1878 char *variantNames[] = VARIANT_NAMES;
1883 return variantNames[v];
1887 /* Identify a variant from the strings the chess servers use or the
1888 PGN Variant tag names we use. */
1895 VariantClass v = VariantNormal;
1896 int i, found = FALSE;
1902 /* [HGM] skip over optional board-size prefixes */
1903 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1904 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1905 while( *e++ != '_');
1908 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1912 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1913 if (StrCaseStr(e, variantNames[i])) {
1914 v = (VariantClass) i;
1921 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1922 || StrCaseStr(e, "wild/fr")
1923 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1924 v = VariantFischeRandom;
1925 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1926 (i = 1, p = StrCaseStr(e, "w"))) {
1928 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1935 case 0: /* FICS only, actually */
1937 /* Castling legal even if K starts on d-file */
1938 v = VariantWildCastle;
1943 /* Castling illegal even if K & R happen to start in
1944 normal positions. */
1945 v = VariantNoCastle;
1958 /* Castling legal iff K & R start in normal positions */
1964 /* Special wilds for position setup; unclear what to do here */
1965 v = VariantLoadable;
1968 /* Bizarre ICC game */
1969 v = VariantTwoKings;
1972 v = VariantKriegspiel;
1978 v = VariantFischeRandom;
1981 v = VariantCrazyhouse;
1984 v = VariantBughouse;
1990 /* Not quite the same as FICS suicide! */
1991 v = VariantGiveaway;
1997 v = VariantShatranj;
2000 /* Temporary names for future ICC types. The name *will* change in
2001 the next xboard/WinBoard release after ICC defines it. */
2039 v = VariantCapablanca;
2042 v = VariantKnightmate;
2048 v = VariantCylinder;
2054 v = VariantCapaRandom;
2057 v = VariantBerolina;
2069 /* Found "wild" or "w" in the string but no number;
2070 must assume it's normal chess. */
2074 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2075 if( (len > MSG_SIZ) && appData.debugMode )
2076 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2078 DisplayError(buf, 0);
2084 if (appData.debugMode) {
2085 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2086 e, wnum, VariantName(v));
2091 static int leftover_start = 0, leftover_len = 0;
2092 char star_match[STAR_MATCH_N][MSG_SIZ];
2094 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2095 advance *index beyond it, and set leftover_start to the new value of
2096 *index; else return FALSE. If pattern contains the character '*', it
2097 matches any sequence of characters not containing '\r', '\n', or the
2098 character following the '*' (if any), and the matched sequence(s) are
2099 copied into star_match.
2102 looking_at(buf, index, pattern)
2107 char *bufp = &buf[*index], *patternp = pattern;
2109 char *matchp = star_match[0];
2112 if (*patternp == NULLCHAR) {
2113 *index = leftover_start = bufp - buf;
2117 if (*bufp == NULLCHAR) return FALSE;
2118 if (*patternp == '*') {
2119 if (*bufp == *(patternp + 1)) {
2121 matchp = star_match[++star_count];
2125 } else if (*bufp == '\n' || *bufp == '\r') {
2127 if (*patternp == NULLCHAR)
2132 *matchp++ = *bufp++;
2136 if (*patternp != *bufp) return FALSE;
2143 SendToPlayer(data, length)
2147 int error, outCount;
2148 outCount = OutputToProcess(NoProc, data, length, &error);
2149 if (outCount < length) {
2150 DisplayFatalError(_("Error writing to display"), error, 1);
2155 PackHolding(packed, holding)
2167 switch (runlength) {
2178 sprintf(q, "%d", runlength);
2190 /* Telnet protocol requests from the front end */
2192 TelnetRequest(ddww, option)
2193 unsigned char ddww, option;
2195 unsigned char msg[3];
2196 int outCount, outError;
2198 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2200 if (appData.debugMode) {
2201 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2217 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2226 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2229 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2234 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2236 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2243 if (!appData.icsActive) return;
2244 TelnetRequest(TN_DO, TN_ECHO);
2250 if (!appData.icsActive) return;
2251 TelnetRequest(TN_DONT, TN_ECHO);
2255 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2257 /* put the holdings sent to us by the server on the board holdings area */
2258 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2262 if(gameInfo.holdingsWidth < 2) return;
2263 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2264 return; // prevent overwriting by pre-board holdings
2266 if( (int)lowestPiece >= BlackPawn ) {
2269 holdingsStartRow = BOARD_HEIGHT-1;
2272 holdingsColumn = BOARD_WIDTH-1;
2273 countsColumn = BOARD_WIDTH-2;
2274 holdingsStartRow = 0;
2278 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2279 board[i][holdingsColumn] = EmptySquare;
2280 board[i][countsColumn] = (ChessSquare) 0;
2282 while( (p=*holdings++) != NULLCHAR ) {
2283 piece = CharToPiece( ToUpper(p) );
2284 if(piece == EmptySquare) continue;
2285 /*j = (int) piece - (int) WhitePawn;*/
2286 j = PieceToNumber(piece);
2287 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2288 if(j < 0) continue; /* should not happen */
2289 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2290 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2291 board[holdingsStartRow+j*direction][countsColumn]++;
2297 VariantSwitch(Board board, VariantClass newVariant)
2299 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2300 static Board oldBoard;
2302 startedFromPositionFile = FALSE;
2303 if(gameInfo.variant == newVariant) return;
2305 /* [HGM] This routine is called each time an assignment is made to
2306 * gameInfo.variant during a game, to make sure the board sizes
2307 * are set to match the new variant. If that means adding or deleting
2308 * holdings, we shift the playing board accordingly
2309 * This kludge is needed because in ICS observe mode, we get boards
2310 * of an ongoing game without knowing the variant, and learn about the
2311 * latter only later. This can be because of the move list we requested,
2312 * in which case the game history is refilled from the beginning anyway,
2313 * but also when receiving holdings of a crazyhouse game. In the latter
2314 * case we want to add those holdings to the already received position.
2318 if (appData.debugMode) {
2319 fprintf(debugFP, "Switch board from %s to %s\n",
2320 VariantName(gameInfo.variant), VariantName(newVariant));
2321 setbuf(debugFP, NULL);
2323 shuffleOpenings = 0; /* [HGM] shuffle */
2324 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2328 newWidth = 9; newHeight = 9;
2329 gameInfo.holdingsSize = 7;
2330 case VariantBughouse:
2331 case VariantCrazyhouse:
2332 newHoldingsWidth = 2; break;
2336 newHoldingsWidth = 2;
2337 gameInfo.holdingsSize = 8;
2340 case VariantCapablanca:
2341 case VariantCapaRandom:
2344 newHoldingsWidth = gameInfo.holdingsSize = 0;
2347 if(newWidth != gameInfo.boardWidth ||
2348 newHeight != gameInfo.boardHeight ||
2349 newHoldingsWidth != gameInfo.holdingsWidth ) {
2351 /* shift position to new playing area, if needed */
2352 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2353 for(i=0; i<BOARD_HEIGHT; i++)
2354 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2355 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2357 for(i=0; i<newHeight; i++) {
2358 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2359 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2361 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2362 for(i=0; i<BOARD_HEIGHT; i++)
2363 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2364 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2367 gameInfo.boardWidth = newWidth;
2368 gameInfo.boardHeight = newHeight;
2369 gameInfo.holdingsWidth = newHoldingsWidth;
2370 gameInfo.variant = newVariant;
2371 InitDrawingSizes(-2, 0);
2372 } else gameInfo.variant = newVariant;
2373 CopyBoard(oldBoard, board); // remember correctly formatted board
2374 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2375 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2378 static int loggedOn = FALSE;
2380 /*-- Game start info cache: --*/
2382 char gs_kind[MSG_SIZ];
2383 static char player1Name[128] = "";
2384 static char player2Name[128] = "";
2385 static char cont_seq[] = "\n\\ ";
2386 static int player1Rating = -1;
2387 static int player2Rating = -1;
2388 /*----------------------------*/
2390 ColorClass curColor = ColorNormal;
2391 int suppressKibitz = 0;
2394 Boolean soughtPending = FALSE;
2395 Boolean seekGraphUp;
2396 #define MAX_SEEK_ADS 200
2398 char *seekAdList[MAX_SEEK_ADS];
2399 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2400 float tcList[MAX_SEEK_ADS];
2401 char colorList[MAX_SEEK_ADS];
2402 int nrOfSeekAds = 0;
2403 int minRating = 1010, maxRating = 2800;
2404 int hMargin = 10, vMargin = 20, h, w;
2405 extern int squareSize, lineGap;
2410 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2411 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2412 if(r < minRating+100 && r >=0 ) r = minRating+100;
2413 if(r > maxRating) r = maxRating;
2414 if(tc < 1.) tc = 1.;
2415 if(tc > 95.) tc = 95.;
2416 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2417 y = ((double)r - minRating)/(maxRating - minRating)
2418 * (h-vMargin-squareSize/8-1) + vMargin;
2419 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2420 if(strstr(seekAdList[i], " u ")) color = 1;
2421 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2422 !strstr(seekAdList[i], "bullet") &&
2423 !strstr(seekAdList[i], "blitz") &&
2424 !strstr(seekAdList[i], "standard") ) color = 2;
2425 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2426 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2430 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2432 char buf[MSG_SIZ], *ext = "";
2433 VariantClass v = StringToVariant(type);
2434 if(strstr(type, "wild")) {
2435 ext = type + 4; // append wild number
2436 if(v == VariantFischeRandom) type = "chess960"; else
2437 if(v == VariantLoadable) type = "setup"; else
2438 type = VariantName(v);
2440 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2441 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2442 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2443 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2444 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2445 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2446 seekNrList[nrOfSeekAds] = nr;
2447 zList[nrOfSeekAds] = 0;
2448 seekAdList[nrOfSeekAds++] = StrSave(buf);
2449 if(plot) PlotSeekAd(nrOfSeekAds-1);
2456 int x = xList[i], y = yList[i], d=squareSize/4, k;
2457 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2458 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2459 // now replot every dot that overlapped
2460 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2461 int xx = xList[k], yy = yList[k];
2462 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2463 DrawSeekDot(xx, yy, colorList[k]);
2468 RemoveSeekAd(int nr)
2471 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2473 if(seekAdList[i]) free(seekAdList[i]);
2474 seekAdList[i] = seekAdList[--nrOfSeekAds];
2475 seekNrList[i] = seekNrList[nrOfSeekAds];
2476 ratingList[i] = ratingList[nrOfSeekAds];
2477 colorList[i] = colorList[nrOfSeekAds];
2478 tcList[i] = tcList[nrOfSeekAds];
2479 xList[i] = xList[nrOfSeekAds];
2480 yList[i] = yList[nrOfSeekAds];
2481 zList[i] = zList[nrOfSeekAds];
2482 seekAdList[nrOfSeekAds] = NULL;
2488 MatchSoughtLine(char *line)
2490 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2491 int nr, base, inc, u=0; char dummy;
2493 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2494 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2496 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2497 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2498 // match: compact and save the line
2499 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2509 if(!seekGraphUp) return FALSE;
2510 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2511 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2513 DrawSeekBackground(0, 0, w, h);
2514 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2515 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2516 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2517 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2519 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2522 snprintf(buf, MSG_SIZ, "%d", i);
2523 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2526 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2527 for(i=1; i<100; i+=(i<10?1:5)) {
2528 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2529 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2530 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2532 snprintf(buf, MSG_SIZ, "%d", i);
2533 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2536 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2540 int SeekGraphClick(ClickType click, int x, int y, int moving)
2542 static int lastDown = 0, displayed = 0, lastSecond;
2543 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2544 if(click == Release || moving) return FALSE;
2546 soughtPending = TRUE;
2547 SendToICS(ics_prefix);
2548 SendToICS("sought\n"); // should this be "sought all"?
2549 } else { // issue challenge based on clicked ad
2550 int dist = 10000; int i, closest = 0, second = 0;
2551 for(i=0; i<nrOfSeekAds; i++) {
2552 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2553 if(d < dist) { dist = d; closest = i; }
2554 second += (d - zList[i] < 120); // count in-range ads
2555 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2559 second = (second > 1);
2560 if(displayed != closest || second != lastSecond) {
2561 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2562 lastSecond = second; displayed = closest;
2564 if(click == Press) {
2565 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2568 } // on press 'hit', only show info
2569 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2570 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2571 SendToICS(ics_prefix);
2573 return TRUE; // let incoming board of started game pop down the graph
2574 } else if(click == Release) { // release 'miss' is ignored
2575 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2576 if(moving == 2) { // right up-click
2577 nrOfSeekAds = 0; // refresh graph
2578 soughtPending = TRUE;
2579 SendToICS(ics_prefix);
2580 SendToICS("sought\n"); // should this be "sought all"?
2583 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2584 // press miss or release hit 'pop down' seek graph
2585 seekGraphUp = FALSE;
2586 DrawPosition(TRUE, NULL);
2592 read_from_ics(isr, closure, data, count, error)
2599 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2600 #define STARTED_NONE 0
2601 #define STARTED_MOVES 1
2602 #define STARTED_BOARD 2
2603 #define STARTED_OBSERVE 3
2604 #define STARTED_HOLDINGS 4
2605 #define STARTED_CHATTER 5
2606 #define STARTED_COMMENT 6
2607 #define STARTED_MOVES_NOHIDE 7
2609 static int started = STARTED_NONE;
2610 static char parse[20000];
2611 static int parse_pos = 0;
2612 static char buf[BUF_SIZE + 1];
2613 static int firstTime = TRUE, intfSet = FALSE;
2614 static ColorClass prevColor = ColorNormal;
2615 static int savingComment = FALSE;
2616 static int cmatch = 0; // continuation sequence match
2623 int backup; /* [DM] For zippy color lines */
2625 char talker[MSG_SIZ]; // [HGM] chat
2628 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2630 if (appData.debugMode) {
2632 fprintf(debugFP, "<ICS: ");
2633 show_bytes(debugFP, data, count);
2634 fprintf(debugFP, "\n");
2638 if (appData.debugMode) { int f = forwardMostMove;
2639 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2640 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2641 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2644 /* If last read ended with a partial line that we couldn't parse,
2645 prepend it to the new read and try again. */
2646 if (leftover_len > 0) {
2647 for (i=0; i<leftover_len; i++)
2648 buf[i] = buf[leftover_start + i];
2651 /* copy new characters into the buffer */
2652 bp = buf + leftover_len;
2653 buf_len=leftover_len;
2654 for (i=0; i<count; i++)
2657 if (data[i] == '\r')
2660 // join lines split by ICS?
2661 if (!appData.noJoin)
2664 Joining just consists of finding matches against the
2665 continuation sequence, and discarding that sequence
2666 if found instead of copying it. So, until a match
2667 fails, there's nothing to do since it might be the
2668 complete sequence, and thus, something we don't want
2671 if (data[i] == cont_seq[cmatch])
2674 if (cmatch == strlen(cont_seq))
2676 cmatch = 0; // complete match. just reset the counter
2679 it's possible for the ICS to not include the space
2680 at the end of the last word, making our [correct]
2681 join operation fuse two separate words. the server
2682 does this when the space occurs at the width setting.
2684 if (!buf_len || buf[buf_len-1] != ' ')
2695 match failed, so we have to copy what matched before
2696 falling through and copying this character. In reality,
2697 this will only ever be just the newline character, but
2698 it doesn't hurt to be precise.
2700 strncpy(bp, cont_seq, cmatch);
2712 buf[buf_len] = NULLCHAR;
2713 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2718 while (i < buf_len) {
2719 /* Deal with part of the TELNET option negotiation
2720 protocol. We refuse to do anything beyond the
2721 defaults, except that we allow the WILL ECHO option,
2722 which ICS uses to turn off password echoing when we are
2723 directly connected to it. We reject this option
2724 if localLineEditing mode is on (always on in xboard)
2725 and we are talking to port 23, which might be a real
2726 telnet server that will try to keep WILL ECHO on permanently.
2728 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2729 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2730 unsigned char option;
2732 switch ((unsigned char) buf[++i]) {
2734 if (appData.debugMode)
2735 fprintf(debugFP, "\n<WILL ");
2736 switch (option = (unsigned char) buf[++i]) {
2738 if (appData.debugMode)
2739 fprintf(debugFP, "ECHO ");
2740 /* Reply only if this is a change, according
2741 to the protocol rules. */
2742 if (remoteEchoOption) break;
2743 if (appData.localLineEditing &&
2744 atoi(appData.icsPort) == TN_PORT) {
2745 TelnetRequest(TN_DONT, TN_ECHO);
2748 TelnetRequest(TN_DO, TN_ECHO);
2749 remoteEchoOption = TRUE;
2753 if (appData.debugMode)
2754 fprintf(debugFP, "%d ", option);
2755 /* Whatever this is, we don't want it. */
2756 TelnetRequest(TN_DONT, option);
2761 if (appData.debugMode)
2762 fprintf(debugFP, "\n<WONT ");
2763 switch (option = (unsigned char) buf[++i]) {
2765 if (appData.debugMode)
2766 fprintf(debugFP, "ECHO ");
2767 /* Reply only if this is a change, according
2768 to the protocol rules. */
2769 if (!remoteEchoOption) break;
2771 TelnetRequest(TN_DONT, TN_ECHO);
2772 remoteEchoOption = FALSE;
2775 if (appData.debugMode)
2776 fprintf(debugFP, "%d ", (unsigned char) option);
2777 /* Whatever this is, it must already be turned
2778 off, because we never agree to turn on
2779 anything non-default, so according to the
2780 protocol rules, we don't reply. */
2785 if (appData.debugMode)
2786 fprintf(debugFP, "\n<DO ");
2787 switch (option = (unsigned char) buf[++i]) {
2789 /* Whatever this is, we refuse to do it. */
2790 if (appData.debugMode)
2791 fprintf(debugFP, "%d ", option);
2792 TelnetRequest(TN_WONT, option);
2797 if (appData.debugMode)
2798 fprintf(debugFP, "\n<DONT ");
2799 switch (option = (unsigned char) buf[++i]) {
2801 if (appData.debugMode)
2802 fprintf(debugFP, "%d ", option);
2803 /* Whatever this is, we are already not doing
2804 it, because we never agree to do anything
2805 non-default, so according to the protocol
2806 rules, we don't reply. */
2811 if (appData.debugMode)
2812 fprintf(debugFP, "\n<IAC ");
2813 /* Doubled IAC; pass it through */
2817 if (appData.debugMode)
2818 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2819 /* Drop all other telnet commands on the floor */
2822 if (oldi > next_out)
2823 SendToPlayer(&buf[next_out], oldi - next_out);
2829 /* OK, this at least will *usually* work */
2830 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2834 if (loggedOn && !intfSet) {
2835 if (ics_type == ICS_ICC) {
2836 snprintf(str, MSG_SIZ,
2837 "/set-quietly interface %s\n/set-quietly style 12\n",
2839 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2840 strcat(str, "/set-2 51 1\n/set seek 1\n");
2841 } else if (ics_type == ICS_CHESSNET) {
2842 snprintf(str, MSG_SIZ, "/style 12\n");
2844 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2845 strcat(str, programVersion);
2846 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2847 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2848 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2850 strcat(str, "$iset nohighlight 1\n");
2852 strcat(str, "$iset lock 1\n$style 12\n");
2855 NotifyFrontendLogin();
2859 if (started == STARTED_COMMENT) {
2860 /* Accumulate characters in comment */
2861 parse[parse_pos++] = buf[i];
2862 if (buf[i] == '\n') {
2863 parse[parse_pos] = NULLCHAR;
2864 if(chattingPartner>=0) {
2866 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2867 OutputChatMessage(chattingPartner, mess);
2868 chattingPartner = -1;
2869 next_out = i+1; // [HGM] suppress printing in ICS window
2871 if(!suppressKibitz) // [HGM] kibitz
2872 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2873 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2874 int nrDigit = 0, nrAlph = 0, j;
2875 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2876 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2877 parse[parse_pos] = NULLCHAR;
2878 // try to be smart: if it does not look like search info, it should go to
2879 // ICS interaction window after all, not to engine-output window.
2880 for(j=0; j<parse_pos; j++) { // count letters and digits
2881 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2882 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2883 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2885 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2886 int depth=0; float score;
2887 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2888 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2889 pvInfoList[forwardMostMove-1].depth = depth;
2890 pvInfoList[forwardMostMove-1].score = 100*score;
2892 OutputKibitz(suppressKibitz, parse);
2895 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2896 SendToPlayer(tmp, strlen(tmp));
2898 next_out = i+1; // [HGM] suppress printing in ICS window
2900 started = STARTED_NONE;
2902 /* Don't match patterns against characters in comment */
2907 if (started == STARTED_CHATTER) {
2908 if (buf[i] != '\n') {
2909 /* Don't match patterns against characters in chatter */
2913 started = STARTED_NONE;
2914 if(suppressKibitz) next_out = i+1;
2917 /* Kludge to deal with rcmd protocol */
2918 if (firstTime && looking_at(buf, &i, "\001*")) {
2919 DisplayFatalError(&buf[1], 0, 1);
2925 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2928 if (appData.debugMode)
2929 fprintf(debugFP, "ics_type %d\n", ics_type);
2932 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2933 ics_type = ICS_FICS;
2935 if (appData.debugMode)
2936 fprintf(debugFP, "ics_type %d\n", ics_type);
2939 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2940 ics_type = ICS_CHESSNET;
2942 if (appData.debugMode)
2943 fprintf(debugFP, "ics_type %d\n", ics_type);
2948 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2949 looking_at(buf, &i, "Logging you in as \"*\"") ||
2950 looking_at(buf, &i, "will be \"*\""))) {
2951 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2955 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2957 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2958 DisplayIcsInteractionTitle(buf);
2959 have_set_title = TRUE;
2962 /* skip finger notes */
2963 if (started == STARTED_NONE &&
2964 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2965 (buf[i] == '1' && buf[i+1] == '0')) &&
2966 buf[i+2] == ':' && buf[i+3] == ' ') {
2967 started = STARTED_CHATTER;
2973 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2974 if(appData.seekGraph) {
2975 if(soughtPending && MatchSoughtLine(buf+i)) {
2976 i = strstr(buf+i, "rated") - buf;
2977 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978 next_out = leftover_start = i;
2979 started = STARTED_CHATTER;
2980 suppressKibitz = TRUE;
2983 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2984 && looking_at(buf, &i, "* ads displayed")) {
2985 soughtPending = FALSE;
2990 if(appData.autoRefresh) {
2991 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2992 int s = (ics_type == ICS_ICC); // ICC format differs
2994 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2995 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2996 looking_at(buf, &i, "*% "); // eat prompt
2997 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2998 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2999 next_out = i; // suppress
3002 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3003 char *p = star_match[0];
3005 if(seekGraphUp) RemoveSeekAd(atoi(p));
3006 while(*p && *p++ != ' '); // next
3008 looking_at(buf, &i, "*% "); // eat prompt
3009 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3016 /* skip formula vars */
3017 if (started == STARTED_NONE &&
3018 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3019 started = STARTED_CHATTER;
3024 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3025 if (appData.autoKibitz && started == STARTED_NONE &&
3026 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3027 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3028 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3029 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3030 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3031 suppressKibitz = TRUE;
3032 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3034 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3035 && (gameMode == IcsPlayingWhite)) ||
3036 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3037 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3038 started = STARTED_CHATTER; // own kibitz we simply discard
3040 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3041 parse_pos = 0; parse[0] = NULLCHAR;
3042 savingComment = TRUE;
3043 suppressKibitz = gameMode != IcsObserving ? 2 :
3044 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3048 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3049 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3050 && atoi(star_match[0])) {
3051 // suppress the acknowledgements of our own autoKibitz
3053 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3055 SendToPlayer(star_match[0], strlen(star_match[0]));
3056 if(looking_at(buf, &i, "*% ")) // eat prompt
3057 suppressKibitz = FALSE;
3061 } // [HGM] kibitz: end of patch
3063 // [HGM] chat: intercept tells by users for which we have an open chat window
3065 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3066 looking_at(buf, &i, "* whispers:") ||
3067 looking_at(buf, &i, "* kibitzes:") ||
3068 looking_at(buf, &i, "* shouts:") ||
3069 looking_at(buf, &i, "* c-shouts:") ||
3070 looking_at(buf, &i, "--> * ") ||
3071 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3072 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3073 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3074 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3076 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3077 chattingPartner = -1;
3079 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3080 for(p=0; p<MAX_CHAT; p++) {
3081 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3082 talker[0] = '['; strcat(talker, "] ");
3083 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3084 chattingPartner = p; break;
3087 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3088 for(p=0; p<MAX_CHAT; p++) {
3089 if(!strcmp("kibitzes", chatPartner[p])) {
3090 talker[0] = '['; strcat(talker, "] ");
3091 chattingPartner = p; break;
3094 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3095 for(p=0; p<MAX_CHAT; p++) {
3096 if(!strcmp("whispers", chatPartner[p])) {
3097 talker[0] = '['; strcat(talker, "] ");
3098 chattingPartner = p; break;
3101 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3102 if(buf[i-8] == '-' && buf[i-3] == 't')
3103 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3104 if(!strcmp("c-shouts", chatPartner[p])) {
3105 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3106 chattingPartner = p; break;
3109 if(chattingPartner < 0)
3110 for(p=0; p<MAX_CHAT; p++) {
3111 if(!strcmp("shouts", chatPartner[p])) {
3112 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3113 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3114 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3115 chattingPartner = p; break;
3119 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3120 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3121 talker[0] = 0; Colorize(ColorTell, FALSE);
3122 chattingPartner = p; break;
3124 if(chattingPartner<0) i = oldi; else {
3125 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3126 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3127 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128 started = STARTED_COMMENT;
3129 parse_pos = 0; parse[0] = NULLCHAR;
3130 savingComment = 3 + chattingPartner; // counts as TRUE
3131 suppressKibitz = TRUE;
3134 } // [HGM] chat: end of patch
3137 if (appData.zippyTalk || appData.zippyPlay) {
3138 /* [DM] Backup address for color zippy lines */
3140 if (loggedOn == TRUE)
3141 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3142 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3144 } // [DM] 'else { ' deleted
3146 /* Regular tells and says */
3147 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3148 looking_at(buf, &i, "* (your partner) tells you: ") ||
3149 looking_at(buf, &i, "* says: ") ||
3150 /* Don't color "message" or "messages" output */
3151 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3152 looking_at(buf, &i, "*. * at *:*: ") ||
3153 looking_at(buf, &i, "--* (*:*): ") ||
3154 /* Message notifications (same color as tells) */
3155 looking_at(buf, &i, "* has left a message ") ||
3156 looking_at(buf, &i, "* just sent you a message:\n") ||
3157 /* Whispers and kibitzes */
3158 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3159 looking_at(buf, &i, "* kibitzes: ") ||
3161 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3163 if (tkind == 1 && strchr(star_match[0], ':')) {
3164 /* Avoid "tells you:" spoofs in channels */
3167 if (star_match[0][0] == NULLCHAR ||
3168 strchr(star_match[0], ' ') ||
3169 (tkind == 3 && strchr(star_match[1], ' '))) {
3170 /* Reject bogus matches */
3173 if (appData.colorize) {
3174 if (oldi > next_out) {
3175 SendToPlayer(&buf[next_out], oldi - next_out);
3180 Colorize(ColorTell, FALSE);
3181 curColor = ColorTell;
3184 Colorize(ColorKibitz, FALSE);
3185 curColor = ColorKibitz;
3188 p = strrchr(star_match[1], '(');
3195 Colorize(ColorChannel1, FALSE);
3196 curColor = ColorChannel1;
3198 Colorize(ColorChannel, FALSE);
3199 curColor = ColorChannel;
3203 curColor = ColorNormal;
3207 if (started == STARTED_NONE && appData.autoComment &&
3208 (gameMode == IcsObserving ||
3209 gameMode == IcsPlayingWhite ||
3210 gameMode == IcsPlayingBlack)) {
3211 parse_pos = i - oldi;
3212 memcpy(parse, &buf[oldi], parse_pos);
3213 parse[parse_pos] = NULLCHAR;
3214 started = STARTED_COMMENT;
3215 savingComment = TRUE;
3217 started = STARTED_CHATTER;
3218 savingComment = FALSE;
3225 if (looking_at(buf, &i, "* s-shouts: ") ||
3226 looking_at(buf, &i, "* c-shouts: ")) {
3227 if (appData.colorize) {
3228 if (oldi > next_out) {
3229 SendToPlayer(&buf[next_out], oldi - next_out);
3232 Colorize(ColorSShout, FALSE);
3233 curColor = ColorSShout;
3236 started = STARTED_CHATTER;
3240 if (looking_at(buf, &i, "--->")) {
3245 if (looking_at(buf, &i, "* shouts: ") ||
3246 looking_at(buf, &i, "--> ")) {
3247 if (appData.colorize) {
3248 if (oldi > next_out) {
3249 SendToPlayer(&buf[next_out], oldi - next_out);
3252 Colorize(ColorShout, FALSE);
3253 curColor = ColorShout;
3256 started = STARTED_CHATTER;
3260 if (looking_at( buf, &i, "Challenge:")) {
3261 if (appData.colorize) {
3262 if (oldi > next_out) {
3263 SendToPlayer(&buf[next_out], oldi - next_out);
3266 Colorize(ColorChallenge, FALSE);
3267 curColor = ColorChallenge;
3273 if (looking_at(buf, &i, "* offers you") ||
3274 looking_at(buf, &i, "* offers to be") ||
3275 looking_at(buf, &i, "* would like to") ||
3276 looking_at(buf, &i, "* requests to") ||
3277 looking_at(buf, &i, "Your opponent offers") ||
3278 looking_at(buf, &i, "Your opponent requests")) {
3280 if (appData.colorize) {
3281 if (oldi > next_out) {
3282 SendToPlayer(&buf[next_out], oldi - next_out);
3285 Colorize(ColorRequest, FALSE);
3286 curColor = ColorRequest;
3291 if (looking_at(buf, &i, "* (*) seeking")) {
3292 if (appData.colorize) {
3293 if (oldi > next_out) {
3294 SendToPlayer(&buf[next_out], oldi - next_out);
3297 Colorize(ColorSeek, FALSE);
3298 curColor = ColorSeek;
3303 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3305 if (looking_at(buf, &i, "\\ ")) {
3306 if (prevColor != ColorNormal) {
3307 if (oldi > next_out) {
3308 SendToPlayer(&buf[next_out], oldi - next_out);
3311 Colorize(prevColor, TRUE);
3312 curColor = prevColor;
3314 if (savingComment) {
3315 parse_pos = i - oldi;
3316 memcpy(parse, &buf[oldi], parse_pos);
3317 parse[parse_pos] = NULLCHAR;
3318 started = STARTED_COMMENT;
3319 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3320 chattingPartner = savingComment - 3; // kludge to remember the box
3322 started = STARTED_CHATTER;
3327 if (looking_at(buf, &i, "Black Strength :") ||
3328 looking_at(buf, &i, "<<< style 10 board >>>") ||
3329 looking_at(buf, &i, "<10>") ||
3330 looking_at(buf, &i, "#@#")) {
3331 /* Wrong board style */
3333 SendToICS(ics_prefix);
3334 SendToICS("set style 12\n");
3335 SendToICS(ics_prefix);
3336 SendToICS("refresh\n");
3340 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3342 have_sent_ICS_logon = 1;
3346 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3347 (looking_at(buf, &i, "\n<12> ") ||
3348 looking_at(buf, &i, "<12> "))) {
3350 if (oldi > next_out) {
3351 SendToPlayer(&buf[next_out], oldi - next_out);
3354 started = STARTED_BOARD;
3359 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3360 looking_at(buf, &i, "<b1> ")) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 started = STARTED_HOLDINGS;
3370 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3372 /* Header for a move list -- first line */
3374 switch (ics_getting_history) {
3378 case BeginningOfGame:
3379 /* User typed "moves" or "oldmoves" while we
3380 were idle. Pretend we asked for these
3381 moves and soak them up so user can step
3382 through them and/or save them.
3385 gameMode = IcsObserving;
3388 ics_getting_history = H_GOT_UNREQ_HEADER;
3390 case EditGame: /*?*/
3391 case EditPosition: /*?*/
3392 /* Should above feature work in these modes too? */
3393 /* For now it doesn't */
3394 ics_getting_history = H_GOT_UNWANTED_HEADER;
3397 ics_getting_history = H_GOT_UNWANTED_HEADER;
3402 /* Is this the right one? */
3403 if (gameInfo.white && gameInfo.black &&
3404 strcmp(gameInfo.white, star_match[0]) == 0 &&
3405 strcmp(gameInfo.black, star_match[2]) == 0) {
3407 ics_getting_history = H_GOT_REQ_HEADER;
3410 case H_GOT_REQ_HEADER:
3411 case H_GOT_UNREQ_HEADER:
3412 case H_GOT_UNWANTED_HEADER:
3413 case H_GETTING_MOVES:
3414 /* Should not happen */
3415 DisplayError(_("Error gathering move list: two headers"), 0);
3416 ics_getting_history = H_FALSE;
3420 /* Save player ratings into gameInfo if needed */
3421 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3422 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3423 (gameInfo.whiteRating == -1 ||
3424 gameInfo.blackRating == -1)) {
3426 gameInfo.whiteRating = string_to_rating(star_match[1]);
3427 gameInfo.blackRating = string_to_rating(star_match[3]);
3428 if (appData.debugMode)
3429 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3430 gameInfo.whiteRating, gameInfo.blackRating);
3435 if (looking_at(buf, &i,
3436 "* * match, initial time: * minute*, increment: * second")) {
3437 /* Header for a move list -- second line */
3438 /* Initial board will follow if this is a wild game */
3439 if (gameInfo.event != NULL) free(gameInfo.event);
3440 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3441 gameInfo.event = StrSave(str);
3442 /* [HGM] we switched variant. Translate boards if needed. */
3443 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3447 if (looking_at(buf, &i, "Move ")) {
3448 /* Beginning of a move list */
3449 switch (ics_getting_history) {
3451 /* Normally should not happen */
3452 /* Maybe user hit reset while we were parsing */
3455 /* Happens if we are ignoring a move list that is not
3456 * the one we just requested. Common if the user
3457 * tries to observe two games without turning off
3460 case H_GETTING_MOVES:
3461 /* Should not happen */
3462 DisplayError(_("Error gathering move list: nested"), 0);
3463 ics_getting_history = H_FALSE;
3465 case H_GOT_REQ_HEADER:
3466 ics_getting_history = H_GETTING_MOVES;
3467 started = STARTED_MOVES;
3469 if (oldi > next_out) {
3470 SendToPlayer(&buf[next_out], oldi - next_out);
3473 case H_GOT_UNREQ_HEADER:
3474 ics_getting_history = H_GETTING_MOVES;
3475 started = STARTED_MOVES_NOHIDE;
3478 case H_GOT_UNWANTED_HEADER:
3479 ics_getting_history = H_FALSE;
3485 if (looking_at(buf, &i, "% ") ||
3486 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3487 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3488 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3489 soughtPending = FALSE;
3493 if(suppressKibitz) next_out = i;
3494 savingComment = FALSE;
3498 case STARTED_MOVES_NOHIDE:
3499 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3500 parse[parse_pos + i - oldi] = NULLCHAR;
3501 ParseGameHistory(parse);
3503 if (appData.zippyPlay && first.initDone) {
3504 FeedMovesToProgram(&first, forwardMostMove);
3505 if (gameMode == IcsPlayingWhite) {
3506 if (WhiteOnMove(forwardMostMove)) {
3507 if (first.sendTime) {
3508 if (first.useColors) {
3509 SendToProgram("black\n", &first);
3511 SendTimeRemaining(&first, TRUE);
3513 if (first.useColors) {
3514 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3516 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3517 first.maybeThinking = TRUE;
3519 if (first.usePlayother) {
3520 if (first.sendTime) {
3521 SendTimeRemaining(&first, TRUE);
3523 SendToProgram("playother\n", &first);
3529 } else if (gameMode == IcsPlayingBlack) {
3530 if (!WhiteOnMove(forwardMostMove)) {
3531 if (first.sendTime) {
3532 if (first.useColors) {
3533 SendToProgram("white\n", &first);
3535 SendTimeRemaining(&first, FALSE);
3537 if (first.useColors) {
3538 SendToProgram("black\n", &first);
3540 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3541 first.maybeThinking = TRUE;
3543 if (first.usePlayother) {
3544 if (first.sendTime) {
3545 SendTimeRemaining(&first, FALSE);
3547 SendToProgram("playother\n", &first);
3556 if (gameMode == IcsObserving && ics_gamenum == -1) {
3557 /* Moves came from oldmoves or moves command
3558 while we weren't doing anything else.
3560 currentMove = forwardMostMove;
3561 ClearHighlights();/*!!could figure this out*/
3562 flipView = appData.flipView;
3563 DrawPosition(TRUE, boards[currentMove]);
3564 DisplayBothClocks();
3565 snprintf(str, MSG_SIZ, "%s vs. %s",
3566 gameInfo.white, gameInfo.black);
3570 /* Moves were history of an active game */
3571 if (gameInfo.resultDetails != NULL) {
3572 free(gameInfo.resultDetails);
3573 gameInfo.resultDetails = NULL;
3576 HistorySet(parseList, backwardMostMove,
3577 forwardMostMove, currentMove-1);
3578 DisplayMove(currentMove - 1);
3579 if (started == STARTED_MOVES) next_out = i;
3580 started = STARTED_NONE;
3581 ics_getting_history = H_FALSE;
3584 case STARTED_OBSERVE:
3585 started = STARTED_NONE;
3586 SendToICS(ics_prefix);
3587 SendToICS("refresh\n");
3593 if(bookHit) { // [HGM] book: simulate book reply
3594 static char bookMove[MSG_SIZ]; // a bit generous?
3596 programStats.nodes = programStats.depth = programStats.time =
3597 programStats.score = programStats.got_only_move = 0;
3598 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3600 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3601 strcat(bookMove, bookHit);
3602 HandleMachineMove(bookMove, &first);
3607 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3608 started == STARTED_HOLDINGS ||
3609 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3610 /* Accumulate characters in move list or board */
3611 parse[parse_pos++] = buf[i];
3614 /* Start of game messages. Mostly we detect start of game
3615 when the first board image arrives. On some versions
3616 of the ICS, though, we need to do a "refresh" after starting
3617 to observe in order to get the current board right away. */
3618 if (looking_at(buf, &i, "Adding game * to observation list")) {
3619 started = STARTED_OBSERVE;
3623 /* Handle auto-observe */
3624 if (appData.autoObserve &&
3625 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3626 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3628 /* Choose the player that was highlighted, if any. */
3629 if (star_match[0][0] == '\033' ||
3630 star_match[1][0] != '\033') {
3631 player = star_match[0];
3633 player = star_match[2];
3635 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3636 ics_prefix, StripHighlightAndTitle(player));
3639 /* Save ratings from notify string */
3640 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3641 player1Rating = string_to_rating(star_match[1]);
3642 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3643 player2Rating = string_to_rating(star_match[3]);
3645 if (appData.debugMode)
3647 "Ratings from 'Game notification:' %s %d, %s %d\n",
3648 player1Name, player1Rating,
3649 player2Name, player2Rating);
3654 /* Deal with automatic examine mode after a game,
3655 and with IcsObserving -> IcsExamining transition */
3656 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3657 looking_at(buf, &i, "has made you an examiner of game *")) {
3659 int gamenum = atoi(star_match[0]);
3660 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3661 gamenum == ics_gamenum) {
3662 /* We were already playing or observing this game;
3663 no need to refetch history */
3664 gameMode = IcsExamining;
3666 pauseExamForwardMostMove = forwardMostMove;
3667 } else if (currentMove < forwardMostMove) {
3668 ForwardInner(forwardMostMove);
3671 /* I don't think this case really can happen */
3672 SendToICS(ics_prefix);
3673 SendToICS("refresh\n");
3678 /* Error messages */
3679 // if (ics_user_moved) {
3680 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3681 if (looking_at(buf, &i, "Illegal move") ||
3682 looking_at(buf, &i, "Not a legal move") ||
3683 looking_at(buf, &i, "Your king is in check") ||
3684 looking_at(buf, &i, "It isn't your turn") ||
3685 looking_at(buf, &i, "It is not your move")) {
3687 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3688 currentMove = forwardMostMove-1;
3689 DisplayMove(currentMove - 1); /* before DMError */
3690 DrawPosition(FALSE, boards[currentMove]);
3691 SwitchClocks(forwardMostMove-1); // [HGM] race
3692 DisplayBothClocks();
3694 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3700 if (looking_at(buf, &i, "still have time") ||
3701 looking_at(buf, &i, "not out of time") ||
3702 looking_at(buf, &i, "either player is out of time") ||
3703 looking_at(buf, &i, "has timeseal; checking")) {
3704 /* We must have called his flag a little too soon */
3705 whiteFlag = blackFlag = FALSE;
3709 if (looking_at(buf, &i, "added * seconds to") ||
3710 looking_at(buf, &i, "seconds were added to")) {
3711 /* Update the clocks */
3712 SendToICS(ics_prefix);
3713 SendToICS("refresh\n");
3717 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3718 ics_clock_paused = TRUE;
3723 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3724 ics_clock_paused = FALSE;
3729 /* Grab player ratings from the Creating: message.
3730 Note we have to check for the special case when
3731 the ICS inserts things like [white] or [black]. */
3732 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3733 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3735 0 player 1 name (not necessarily white)
3737 2 empty, white, or black (IGNORED)
3738 3 player 2 name (not necessarily black)
3741 The names/ratings are sorted out when the game
3742 actually starts (below).
3744 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3745 player1Rating = string_to_rating(star_match[1]);
3746 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3747 player2Rating = string_to_rating(star_match[4]);
3749 if (appData.debugMode)
3751 "Ratings from 'Creating:' %s %d, %s %d\n",
3752 player1Name, player1Rating,
3753 player2Name, player2Rating);
3758 /* Improved generic start/end-of-game messages */
3759 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3760 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3761 /* If tkind == 0: */
3762 /* star_match[0] is the game number */
3763 /* [1] is the white player's name */
3764 /* [2] is the black player's name */
3765 /* For end-of-game: */
3766 /* [3] is the reason for the game end */
3767 /* [4] is a PGN end game-token, preceded by " " */
3768 /* For start-of-game: */
3769 /* [3] begins with "Creating" or "Continuing" */
3770 /* [4] is " *" or empty (don't care). */
3771 int gamenum = atoi(star_match[0]);
3772 char *whitename, *blackname, *why, *endtoken;
3773 ChessMove endtype = EndOfFile;
3776 whitename = star_match[1];
3777 blackname = star_match[2];
3778 why = star_match[3];
3779 endtoken = star_match[4];
3781 whitename = star_match[1];
3782 blackname = star_match[3];
3783 why = star_match[5];
3784 endtoken = star_match[6];
3787 /* Game start messages */
3788 if (strncmp(why, "Creating ", 9) == 0 ||
3789 strncmp(why, "Continuing ", 11) == 0) {
3790 gs_gamenum = gamenum;
3791 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3792 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3794 if (appData.zippyPlay) {
3795 ZippyGameStart(whitename, blackname);
3798 partnerBoardValid = FALSE; // [HGM] bughouse
3802 /* Game end messages */
3803 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3804 ics_gamenum != gamenum) {
3807 while (endtoken[0] == ' ') endtoken++;
3808 switch (endtoken[0]) {
3811 endtype = GameUnfinished;
3814 endtype = BlackWins;
3817 if (endtoken[1] == '/')
3818 endtype = GameIsDrawn;
3820 endtype = WhiteWins;
3823 GameEnds(endtype, why, GE_ICS);
3825 if (appData.zippyPlay && first.initDone) {
3826 ZippyGameEnd(endtype, why);
3827 if (first.pr == NULL) {
3828 /* Start the next process early so that we'll
3829 be ready for the next challenge */
3830 StartChessProgram(&first);
3832 /* Send "new" early, in case this command takes
3833 a long time to finish, so that we'll be ready
3834 for the next challenge. */
3835 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3839 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3843 if (looking_at(buf, &i, "Removing game * from observation") ||
3844 looking_at(buf, &i, "no longer observing game *") ||
3845 looking_at(buf, &i, "Game * (*) has no examiners")) {
3846 if (gameMode == IcsObserving &&
3847 atoi(star_match[0]) == ics_gamenum)
3849 /* icsEngineAnalyze */
3850 if (appData.icsEngineAnalyze) {
3857 ics_user_moved = FALSE;
3862 if (looking_at(buf, &i, "no longer examining game *")) {
3863 if (gameMode == IcsExamining &&
3864 atoi(star_match[0]) == ics_gamenum)
3868 ics_user_moved = FALSE;
3873 /* Advance leftover_start past any newlines we find,
3874 so only partial lines can get reparsed */
3875 if (looking_at(buf, &i, "\n")) {
3876 prevColor = curColor;
3877 if (curColor != ColorNormal) {
3878 if (oldi > next_out) {
3879 SendToPlayer(&buf[next_out], oldi - next_out);
3882 Colorize(ColorNormal, FALSE);
3883 curColor = ColorNormal;
3885 if (started == STARTED_BOARD) {
3886 started = STARTED_NONE;
3887 parse[parse_pos] = NULLCHAR;
3888 ParseBoard12(parse);
3891 /* Send premove here */
3892 if (appData.premove) {
3894 if (currentMove == 0 &&
3895 gameMode == IcsPlayingWhite &&
3896 appData.premoveWhite) {
3897 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3898 if (appData.debugMode)
3899 fprintf(debugFP, "Sending premove:\n");
3901 } else if (currentMove == 1 &&
3902 gameMode == IcsPlayingBlack &&
3903 appData.premoveBlack) {
3904 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3905 if (appData.debugMode)
3906 fprintf(debugFP, "Sending premove:\n");
3908 } else if (gotPremove) {
3910 ClearPremoveHighlights();
3911 if (appData.debugMode)
3912 fprintf(debugFP, "Sending premove:\n");
3913 UserMoveEvent(premoveFromX, premoveFromY,
3914 premoveToX, premoveToY,
3919 /* Usually suppress following prompt */
3920 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3921 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3922 if (looking_at(buf, &i, "*% ")) {
3923 savingComment = FALSE;
3928 } else if (started == STARTED_HOLDINGS) {
3930 char new_piece[MSG_SIZ];
3931 started = STARTED_NONE;
3932 parse[parse_pos] = NULLCHAR;
3933 if (appData.debugMode)
3934 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3935 parse, currentMove);
3936 if (sscanf(parse, " game %d", &gamenum) == 1) {
3937 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3938 if (gameInfo.variant == VariantNormal) {
3939 /* [HGM] We seem to switch variant during a game!
3940 * Presumably no holdings were displayed, so we have
3941 * to move the position two files to the right to
3942 * create room for them!
3944 VariantClass newVariant;
3945 switch(gameInfo.boardWidth) { // base guess on board width
3946 case 9: newVariant = VariantShogi; break;
3947 case 10: newVariant = VariantGreat; break;
3948 default: newVariant = VariantCrazyhouse; break;
3950 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3951 /* Get a move list just to see the header, which
3952 will tell us whether this is really bug or zh */
3953 if (ics_getting_history == H_FALSE) {
3954 ics_getting_history = H_REQUESTED;
3955 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3959 new_piece[0] = NULLCHAR;
3960 sscanf(parse, "game %d white [%s black [%s <- %s",
3961 &gamenum, white_holding, black_holding,
3963 white_holding[strlen(white_holding)-1] = NULLCHAR;
3964 black_holding[strlen(black_holding)-1] = NULLCHAR;
3965 /* [HGM] copy holdings to board holdings area */
3966 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3967 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3968 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3970 if (appData.zippyPlay && first.initDone) {
3971 ZippyHoldings(white_holding, black_holding,
3975 if (tinyLayout || smallLayout) {
3976 char wh[16], bh[16];
3977 PackHolding(wh, white_holding);
3978 PackHolding(bh, black_holding);
3979 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3980 gameInfo.white, gameInfo.black);
3982 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3983 gameInfo.white, white_holding,
3984 gameInfo.black, black_holding);
3986 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3987 DrawPosition(FALSE, boards[currentMove]);
3989 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3990 sscanf(parse, "game %d white [%s black [%s <- %s",
3991 &gamenum, white_holding, black_holding,
3993 white_holding[strlen(white_holding)-1] = NULLCHAR;
3994 black_holding[strlen(black_holding)-1] = NULLCHAR;
3995 /* [HGM] copy holdings to partner-board holdings area */
3996 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3997 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3998 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3999 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4000 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4003 /* Suppress following prompt */
4004 if (looking_at(buf, &i, "*% ")) {
4005 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4006 savingComment = FALSE;
4014 i++; /* skip unparsed character and loop back */
4017 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4018 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4019 // SendToPlayer(&buf[next_out], i - next_out);
4020 started != STARTED_HOLDINGS && leftover_start > next_out) {
4021 SendToPlayer(&buf[next_out], leftover_start - next_out);
4025 leftover_len = buf_len - leftover_start;
4026 /* if buffer ends with something we couldn't parse,
4027 reparse it after appending the next read */
4029 } else if (count == 0) {
4030 RemoveInputSource(isr);
4031 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4033 DisplayFatalError(_("Error reading from ICS"), error, 1);
4038 /* Board style 12 looks like this:
4040 <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
4042 * The "<12> " is stripped before it gets to this routine. The two
4043 * trailing 0's (flip state and clock ticking) are later addition, and
4044 * some chess servers may not have them, or may have only the first.
4045 * Additional trailing fields may be added in the future.
4048 #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"
4050 #define RELATION_OBSERVING_PLAYED 0
4051 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4052 #define RELATION_PLAYING_MYMOVE 1
4053 #define RELATION_PLAYING_NOTMYMOVE -1
4054 #define RELATION_EXAMINING 2
4055 #define RELATION_ISOLATED_BOARD -3
4056 #define RELATION_STARTING_POSITION -4 /* FICS only */
4059 ParseBoard12(string)
4062 GameMode newGameMode;
4063 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4064 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4065 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4066 char to_play, board_chars[200];
4067 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4068 char black[32], white[32];
4070 int prevMove = currentMove;
4073 int fromX, fromY, toX, toY;
4075 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4076 char *bookHit = NULL; // [HGM] book
4077 Boolean weird = FALSE, reqFlag = FALSE;
4079 fromX = fromY = toX = toY = -1;
4083 if (appData.debugMode)
4084 fprintf(debugFP, _("Parsing board: %s\n"), string);
4086 move_str[0] = NULLCHAR;
4087 elapsed_time[0] = NULLCHAR;
4088 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4090 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4091 if(string[i] == ' ') { ranks++; files = 0; }
4093 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4096 for(j = 0; j <i; j++) board_chars[j] = string[j];
4097 board_chars[i] = '\0';
4100 n = sscanf(string, PATTERN, &to_play, &double_push,
4101 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4102 &gamenum, white, black, &relation, &basetime, &increment,
4103 &white_stren, &black_stren, &white_time, &black_time,
4104 &moveNum, str, elapsed_time, move_str, &ics_flip,
4108 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4109 DisplayError(str, 0);
4113 /* Convert the move number to internal form */
4114 moveNum = (moveNum - 1) * 2;
4115 if (to_play == 'B') moveNum++;
4116 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4117 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4123 case RELATION_OBSERVING_PLAYED:
4124 case RELATION_OBSERVING_STATIC:
4125 if (gamenum == -1) {
4126 /* Old ICC buglet */
4127 relation = RELATION_OBSERVING_STATIC;
4129 newGameMode = IcsObserving;
4131 case RELATION_PLAYING_MYMOVE:
4132 case RELATION_PLAYING_NOTMYMOVE:
4134 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4135 IcsPlayingWhite : IcsPlayingBlack;
4137 case RELATION_EXAMINING:
4138 newGameMode = IcsExamining;
4140 case RELATION_ISOLATED_BOARD:
4142 /* Just display this board. If user was doing something else,
4143 we will forget about it until the next board comes. */
4144 newGameMode = IcsIdle;
4146 case RELATION_STARTING_POSITION:
4147 newGameMode = gameMode;
4151 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4152 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4153 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4155 for (k = 0; k < ranks; k++) {
4156 for (j = 0; j < files; j++)
4157 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4158 if(gameInfo.holdingsWidth > 1) {
4159 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4160 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4163 CopyBoard(partnerBoard, board);
4164 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4165 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4166 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4167 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4168 if(toSqr = strchr(str, '-')) {
4169 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4170 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4171 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4172 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4173 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4174 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4176 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4177 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4178 DisplayMessage(partnerStatus, "");
4179 partnerBoardValid = TRUE;
4183 /* Modify behavior for initial board display on move listing
4186 switch (ics_getting_history) {
4190 case H_GOT_REQ_HEADER:
4191 case H_GOT_UNREQ_HEADER:
4192 /* This is the initial position of the current game */
4193 gamenum = ics_gamenum;
4194 moveNum = 0; /* old ICS bug workaround */
4195 if (to_play == 'B') {
4196 startedFromSetupPosition = TRUE;
4197 blackPlaysFirst = TRUE;
4199 if (forwardMostMove == 0) forwardMostMove = 1;
4200 if (backwardMostMove == 0) backwardMostMove = 1;
4201 if (currentMove == 0) currentMove = 1;
4203 newGameMode = gameMode;
4204 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4206 case H_GOT_UNWANTED_HEADER:
4207 /* This is an initial board that we don't want */
4209 case H_GETTING_MOVES:
4210 /* Should not happen */
4211 DisplayError(_("Error gathering move list: extra board"), 0);
4212 ics_getting_history = H_FALSE;
4216 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4217 weird && (int)gameInfo.variant < (int)VariantShogi) {
4218 /* [HGM] We seem to have switched variant unexpectedly
4219 * Try to guess new variant from board size
4221 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4222 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4223 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4224 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4225 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4226 if(!weird) newVariant = VariantNormal;
4227 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4228 /* Get a move list just to see the header, which
4229 will tell us whether this is really bug or zh */
4230 if (ics_getting_history == H_FALSE) {
4231 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4232 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4237 /* Take action if this is the first board of a new game, or of a
4238 different game than is currently being displayed. */
4239 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4240 relation == RELATION_ISOLATED_BOARD) {
4242 /* Forget the old game and get the history (if any) of the new one */
4243 if (gameMode != BeginningOfGame) {
4247 if (appData.autoRaiseBoard) BoardToTop();
4249 if (gamenum == -1) {
4250 newGameMode = IcsIdle;
4251 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4252 appData.getMoveList && !reqFlag) {
4253 /* Need to get game history */
4254 ics_getting_history = H_REQUESTED;
4255 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4259 /* Initially flip the board to have black on the bottom if playing
4260 black or if the ICS flip flag is set, but let the user change
4261 it with the Flip View button. */
4262 flipView = appData.autoFlipView ?
4263 (newGameMode == IcsPlayingBlack) || ics_flip :
4266 /* Done with values from previous mode; copy in new ones */
4267 gameMode = newGameMode;
4269 ics_gamenum = gamenum;
4270 if (gamenum == gs_gamenum) {
4271 int klen = strlen(gs_kind);
4272 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4273 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4274 gameInfo.event = StrSave(str);
4276 gameInfo.event = StrSave("ICS game");
4278 gameInfo.site = StrSave(appData.icsHost);
4279 gameInfo.date = PGNDate();
4280 gameInfo.round = StrSave("-");
4281 gameInfo.white = StrSave(white);
4282 gameInfo.black = StrSave(black);
4283 timeControl = basetime * 60 * 1000;
4285 timeIncrement = increment * 1000;
4286 movesPerSession = 0;
4287 gameInfo.timeControl = TimeControlTagValue();
4288 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4289 if (appData.debugMode) {
4290 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4291 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4292 setbuf(debugFP, NULL);
4295 gameInfo.outOfBook = NULL;
4297 /* Do we have the ratings? */
4298 if (strcmp(player1Name, white) == 0 &&
4299 strcmp(player2Name, black) == 0) {
4300 if (appData.debugMode)
4301 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4302 player1Rating, player2Rating);
4303 gameInfo.whiteRating = player1Rating;
4304 gameInfo.blackRating = player2Rating;
4305 } else if (strcmp(player2Name, white) == 0 &&
4306 strcmp(player1Name, black) == 0) {
4307 if (appData.debugMode)
4308 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4309 player2Rating, player1Rating);
4310 gameInfo.whiteRating = player2Rating;
4311 gameInfo.blackRating = player1Rating;
4313 player1Name[0] = player2Name[0] = NULLCHAR;
4315 /* Silence shouts if requested */
4316 if (appData.quietPlay &&
4317 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4318 SendToICS(ics_prefix);
4319 SendToICS("set shout 0\n");
4323 /* Deal with midgame name changes */
4325 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4326 if (gameInfo.white) free(gameInfo.white);
4327 gameInfo.white = StrSave(white);
4329 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4330 if (gameInfo.black) free(gameInfo.black);
4331 gameInfo.black = StrSave(black);
4335 /* Throw away game result if anything actually changes in examine mode */
4336 if (gameMode == IcsExamining && !newGame) {
4337 gameInfo.result = GameUnfinished;
4338 if (gameInfo.resultDetails != NULL) {
4339 free(gameInfo.resultDetails);
4340 gameInfo.resultDetails = NULL;
4344 /* In pausing && IcsExamining mode, we ignore boards coming
4345 in if they are in a different variation than we are. */
4346 if (pauseExamInvalid) return;
4347 if (pausing && gameMode == IcsExamining) {
4348 if (moveNum <= pauseExamForwardMostMove) {
4349 pauseExamInvalid = TRUE;
4350 forwardMostMove = pauseExamForwardMostMove;
4355 if (appData.debugMode) {
4356 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4358 /* Parse the board */
4359 for (k = 0; k < ranks; k++) {
4360 for (j = 0; j < files; j++)
4361 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4362 if(gameInfo.holdingsWidth > 1) {
4363 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4364 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4367 CopyBoard(boards[moveNum], board);
4368 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4370 startedFromSetupPosition =
4371 !CompareBoards(board, initialPosition);
4372 if(startedFromSetupPosition)
4373 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4376 /* [HGM] Set castling rights. Take the outermost Rooks,
4377 to make it also work for FRC opening positions. Note that board12
4378 is really defective for later FRC positions, as it has no way to
4379 indicate which Rook can castle if they are on the same side of King.
4380 For the initial position we grant rights to the outermost Rooks,
4381 and remember thos rights, and we then copy them on positions
4382 later in an FRC game. This means WB might not recognize castlings with
4383 Rooks that have moved back to their original position as illegal,
4384 but in ICS mode that is not its job anyway.
4386 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4387 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4389 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4390 if(board[0][i] == WhiteRook) j = i;
4391 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4392 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4393 if(board[0][i] == WhiteRook) j = i;
4394 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4395 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4396 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4397 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4398 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4399 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4400 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4402 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4403 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4404 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4405 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4406 if(board[BOARD_HEIGHT-1][k] == bKing)
4407 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4408 if(gameInfo.variant == VariantTwoKings) {
4409 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4410 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4411 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4414 r = boards[moveNum][CASTLING][0] = initialRights[0];
4415 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4416 r = boards[moveNum][CASTLING][1] = initialRights[1];
4417 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4418 r = boards[moveNum][CASTLING][3] = initialRights[3];
4419 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4420 r = boards[moveNum][CASTLING][4] = initialRights[4];
4421 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4422 /* wildcastle kludge: always assume King has rights */
4423 r = boards[moveNum][CASTLING][2] = initialRights[2];
4424 r = boards[moveNum][CASTLING][5] = initialRights[5];
4426 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4427 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4430 if (ics_getting_history == H_GOT_REQ_HEADER ||
4431 ics_getting_history == H_GOT_UNREQ_HEADER) {
4432 /* This was an initial position from a move list, not
4433 the current position */
4437 /* Update currentMove and known move number limits */
4438 newMove = newGame || moveNum > forwardMostMove;
4441 forwardMostMove = backwardMostMove = currentMove = moveNum;
4442 if (gameMode == IcsExamining && moveNum == 0) {
4443 /* Workaround for ICS limitation: we are not told the wild
4444 type when starting to examine a game. But if we ask for
4445 the move list, the move list header will tell us */
4446 ics_getting_history = H_REQUESTED;
4447 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4450 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4451 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4453 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4454 /* [HGM] applied this also to an engine that is silently watching */
4455 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4456 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4457 gameInfo.variant == currentlyInitializedVariant) {
4458 takeback = forwardMostMove - moveNum;
4459 for (i = 0; i < takeback; i++) {
4460 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4461 SendToProgram("undo\n", &first);
4466 forwardMostMove = moveNum;
4467 if (!pausing || currentMove > forwardMostMove)
4468 currentMove = forwardMostMove;
4470 /* New part of history that is not contiguous with old part */
4471 if (pausing && gameMode == IcsExamining) {
4472 pauseExamInvalid = TRUE;
4473 forwardMostMove = pauseExamForwardMostMove;
4476 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4478 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4479 // [HGM] when we will receive the move list we now request, it will be
4480 // fed to the engine from the first move on. So if the engine is not
4481 // in the initial position now, bring it there.
4482 InitChessProgram(&first, 0);
4485 ics_getting_history = H_REQUESTED;
4486 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4489 forwardMostMove = backwardMostMove = currentMove = moveNum;
4492 /* Update the clocks */
4493 if (strchr(elapsed_time, '.')) {
4495 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4496 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4498 /* Time is in seconds */
4499 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4500 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4505 if (appData.zippyPlay && newGame &&
4506 gameMode != IcsObserving && gameMode != IcsIdle &&
4507 gameMode != IcsExamining)
4508 ZippyFirstBoard(moveNum, basetime, increment);
4511 /* Put the move on the move list, first converting
4512 to canonical algebraic form. */
4514 if (appData.debugMode) {
4515 if (appData.debugMode) { int f = forwardMostMove;
4516 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4517 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4518 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4520 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4521 fprintf(debugFP, "moveNum = %d\n", moveNum);
4522 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4523 setbuf(debugFP, NULL);
4525 if (moveNum <= backwardMostMove) {
4526 /* We don't know what the board looked like before
4528 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4529 strcat(parseList[moveNum - 1], " ");
4530 strcat(parseList[moveNum - 1], elapsed_time);
4531 moveList[moveNum - 1][0] = NULLCHAR;
4532 } else if (strcmp(move_str, "none") == 0) {
4533 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4534 /* Again, we don't know what the board looked like;
4535 this is really the start of the game. */
4536 parseList[moveNum - 1][0] = NULLCHAR;
4537 moveList[moveNum - 1][0] = NULLCHAR;
4538 backwardMostMove = moveNum;
4539 startedFromSetupPosition = TRUE;
4540 fromX = fromY = toX = toY = -1;
4542 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4543 // So we parse the long-algebraic move string in stead of the SAN move
4544 int valid; char buf[MSG_SIZ], *prom;
4546 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4547 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4548 // str looks something like "Q/a1-a2"; kill the slash
4550 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4551 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4552 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4553 strcat(buf, prom); // long move lacks promo specification!
4554 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4555 if(appData.debugMode)
4556 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4557 safeStrCpy(move_str, buf, MSG_SIZ);
4559 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4560 &fromX, &fromY, &toX, &toY, &promoChar)
4561 || ParseOneMove(buf, moveNum - 1, &moveType,
4562 &fromX, &fromY, &toX, &toY, &promoChar);
4563 // end of long SAN patch
4565 (void) CoordsToAlgebraic(boards[moveNum - 1],
4566 PosFlags(moveNum - 1),
4567 fromY, fromX, toY, toX, promoChar,
4568 parseList[moveNum-1]);
4569 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4575 if(gameInfo.variant != VariantShogi)
4576 strcat(parseList[moveNum - 1], "+");
4579 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4580 strcat(parseList[moveNum - 1], "#");
4583 strcat(parseList[moveNum - 1], " ");
4584 strcat(parseList[moveNum - 1], elapsed_time);
4585 /* currentMoveString is set as a side-effect of ParseOneMove */
4586 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4587 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4588 strcat(moveList[moveNum - 1], "\n");
4590 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4591 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4592 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4593 ChessSquare old, new = boards[moveNum][k][j];
4594 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4595 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4596 if(old == new) continue;
4597 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4598 else if(new == WhiteWazir || new == BlackWazir) {
4599 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4600 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4601 else boards[moveNum][k][j] = old; // preserve type of Gold
4602 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4603 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4606 /* Move from ICS was illegal!? Punt. */
4607 if (appData.debugMode) {
4608 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4609 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4611 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4612 strcat(parseList[moveNum - 1], " ");
4613 strcat(parseList[moveNum - 1], elapsed_time);
4614 moveList[moveNum - 1][0] = NULLCHAR;
4615 fromX = fromY = toX = toY = -1;
4618 if (appData.debugMode) {
4619 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4620 setbuf(debugFP, NULL);
4624 /* Send move to chess program (BEFORE animating it). */
4625 if (appData.zippyPlay && !newGame && newMove &&
4626 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4628 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4629 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4630 if (moveList[moveNum - 1][0] == NULLCHAR) {
4631 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4633 DisplayError(str, 0);
4635 if (first.sendTime) {
4636 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4638 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4639 if (firstMove && !bookHit) {
4641 if (first.useColors) {
4642 SendToProgram(gameMode == IcsPlayingWhite ?
4644 "black\ngo\n", &first);
4646 SendToProgram("go\n", &first);
4648 first.maybeThinking = TRUE;
4651 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4652 if (moveList[moveNum - 1][0] == NULLCHAR) {
4653 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4654 DisplayError(str, 0);
4656 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4657 SendMoveToProgram(moveNum - 1, &first);
4664 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4665 /* If move comes from a remote source, animate it. If it
4666 isn't remote, it will have already been animated. */
4667 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4668 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4670 if (!pausing && appData.highlightLastMove) {
4671 SetHighlights(fromX, fromY, toX, toY);
4675 /* Start the clocks */
4676 whiteFlag = blackFlag = FALSE;
4677 appData.clockMode = !(basetime == 0 && increment == 0);
4679 ics_clock_paused = TRUE;
4681 } else if (ticking == 1) {
4682 ics_clock_paused = FALSE;
4684 if (gameMode == IcsIdle ||
4685 relation == RELATION_OBSERVING_STATIC ||
4686 relation == RELATION_EXAMINING ||
4688 DisplayBothClocks();
4692 /* Display opponents and material strengths */
4693 if (gameInfo.variant != VariantBughouse &&
4694 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4695 if (tinyLayout || smallLayout) {
4696 if(gameInfo.variant == VariantNormal)
4697 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4698 gameInfo.white, white_stren, gameInfo.black, black_stren,
4699 basetime, increment);
4701 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4702 gameInfo.white, white_stren, gameInfo.black, black_stren,
4703 basetime, increment, (int) gameInfo.variant);
4705 if(gameInfo.variant == VariantNormal)
4706 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4707 gameInfo.white, white_stren, gameInfo.black, black_stren,
4708 basetime, increment);
4710 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4711 gameInfo.white, white_stren, gameInfo.black, black_stren,
4712 basetime, increment, VariantName(gameInfo.variant));
4715 if (appData.debugMode) {
4716 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4721 /* Display the board */
4722 if (!pausing && !appData.noGUI) {
4724 if (appData.premove)
4726 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4727 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4728 ClearPremoveHighlights();
4730 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4731 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4732 DrawPosition(j, boards[currentMove]);
4734 DisplayMove(moveNum - 1);
4735 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4736 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4737 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4738 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4742 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4744 if(bookHit) { // [HGM] book: simulate book reply
4745 static char bookMove[MSG_SIZ]; // a bit generous?
4747 programStats.nodes = programStats.depth = programStats.time =
4748 programStats.score = programStats.got_only_move = 0;
4749 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4751 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4752 strcat(bookMove, bookHit);
4753 HandleMachineMove(bookMove, &first);
4762 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4763 ics_getting_history = H_REQUESTED;
4764 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4770 AnalysisPeriodicEvent(force)
4773 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4774 && !force) || !appData.periodicUpdates)
4777 /* Send . command to Crafty to collect stats */
4778 SendToProgram(".\n", &first);
4780 /* Don't send another until we get a response (this makes
4781 us stop sending to old Crafty's which don't understand
4782 the "." command (sending illegal cmds resets node count & time,
4783 which looks bad)) */
4784 programStats.ok_to_send = 0;
4787 void ics_update_width(new_width)
4790 ics_printf("set width %d\n", new_width);
4794 SendMoveToProgram(moveNum, cps)
4796 ChessProgramState *cps;
4800 if (cps->useUsermove) {
4801 SendToProgram("usermove ", cps);
4805 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4806 int len = space - parseList[moveNum];
4807 memcpy(buf, parseList[moveNum], len);
4809 buf[len] = NULLCHAR;
4811 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4813 SendToProgram(buf, cps);
4815 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4816 AlphaRank(moveList[moveNum], 4);
4817 SendToProgram(moveList[moveNum], cps);
4818 AlphaRank(moveList[moveNum], 4); // and back
4820 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4821 * the engine. It would be nice to have a better way to identify castle
4823 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4824 && cps->useOOCastle) {
4825 int fromX = moveList[moveNum][0] - AAA;
4826 int fromY = moveList[moveNum][1] - ONE;
4827 int toX = moveList[moveNum][2] - AAA;
4828 int toY = moveList[moveNum][3] - ONE;
4829 if((boards[moveNum][fromY][fromX] == WhiteKing
4830 && boards[moveNum][toY][toX] == WhiteRook)
4831 || (boards[moveNum][fromY][fromX] == BlackKing
4832 && boards[moveNum][toY][toX] == BlackRook)) {
4833 if(toX > fromX) SendToProgram("O-O\n", cps);
4834 else SendToProgram("O-O-O\n", cps);
4836 else SendToProgram(moveList[moveNum], cps);
4838 else SendToProgram(moveList[moveNum], cps);
4839 /* End of additions by Tord */
4842 /* [HGM] setting up the opening has brought engine in force mode! */
4843 /* Send 'go' if we are in a mode where machine should play. */
4844 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4845 (gameMode == TwoMachinesPlay ||
4847 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4849 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4850 SendToProgram("go\n", cps);
4851 if (appData.debugMode) {
4852 fprintf(debugFP, "(extra)\n");
4855 setboardSpoiledMachineBlack = 0;
4859 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4861 int fromX, fromY, toX, toY;
4864 char user_move[MSG_SIZ];
4868 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4869 (int)moveType, fromX, fromY, toX, toY);
4870 DisplayError(user_move + strlen("say "), 0);
4872 case WhiteKingSideCastle:
4873 case BlackKingSideCastle:
4874 case WhiteQueenSideCastleWild:
4875 case BlackQueenSideCastleWild:
4877 case WhiteHSideCastleFR:
4878 case BlackHSideCastleFR:
4880 snprintf(user_move, MSG_SIZ, "o-o\n");
4882 case WhiteQueenSideCastle:
4883 case BlackQueenSideCastle:
4884 case WhiteKingSideCastleWild:
4885 case BlackKingSideCastleWild:
4887 case WhiteASideCastleFR:
4888 case BlackASideCastleFR:
4890 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4892 case WhiteNonPromotion:
4893 case BlackNonPromotion:
4894 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4896 case WhitePromotion:
4897 case BlackPromotion:
4898 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4899 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4900 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4901 PieceToChar(WhiteFerz));
4902 else if(gameInfo.variant == VariantGreat)
4903 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4904 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4905 PieceToChar(WhiteMan));
4907 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4908 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4914 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4915 ToUpper(PieceToChar((ChessSquare) fromX)),
4916 AAA + toX, ONE + toY);
4918 case IllegalMove: /* could be a variant we don't quite understand */
4919 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4921 case WhiteCapturesEnPassant:
4922 case BlackCapturesEnPassant:
4923 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4924 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4927 SendToICS(user_move);
4928 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4929 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4934 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4935 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4936 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4937 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4938 DisplayError("You cannot do this while you are playing or observing", 0);
4941 if(gameMode != IcsExamining) { // is this ever not the case?
4942 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4944 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4945 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4946 } else { // on FICS we must first go to general examine mode
4947 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4949 if(gameInfo.variant != VariantNormal) {
4950 // try figure out wild number, as xboard names are not always valid on ICS
4951 for(i=1; i<=36; i++) {
4952 snprintf(buf, MSG_SIZ, "wild/%d", i);
4953 if(StringToVariant(buf) == gameInfo.variant) break;
4955 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4956 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4957 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4958 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4959 SendToICS(ics_prefix);
4961 if(startedFromSetupPosition || backwardMostMove != 0) {
4962 fen = PositionToFEN(backwardMostMove, NULL);
4963 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4964 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4966 } else { // FICS: everything has to set by separate bsetup commands
4967 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4968 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4970 if(!WhiteOnMove(backwardMostMove)) {
4971 SendToICS("bsetup tomove black\n");
4973 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4974 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4976 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4977 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4979 i = boards[backwardMostMove][EP_STATUS];
4980 if(i >= 0) { // set e.p.
4981 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4987 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4988 SendToICS("bsetup done\n"); // switch to normal examining.
4990 for(i = backwardMostMove; i<last; i++) {
4992 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4995 SendToICS(ics_prefix);
4996 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5000 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5005 if (rf == DROP_RANK) {
5006 sprintf(move, "%c@%c%c\n",
5007 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5009 if (promoChar == 'x' || promoChar == NULLCHAR) {
5010 sprintf(move, "%c%c%c%c\n",
5011 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5013 sprintf(move, "%c%c%c%c%c\n",
5014 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5020 ProcessICSInitScript(f)
5025 while (fgets(buf, MSG_SIZ, f)) {
5026 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5033 static int lastX, lastY, selectFlag, dragging;
5038 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5039 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5040 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5041 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5042 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5043 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5046 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5047 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5048 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5049 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5051 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5052 appData.testLegality && (promoSweep == king ||
5053 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5054 ChangeDragPiece(promoSweep);
5057 int PromoScroll(int x, int y)
5061 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5062 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5063 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5064 if(!step) return FALSE;
5065 lastX = x; lastY = y;
5066 if((promoSweep < BlackPawn) == flipView) step = -step;
5067 if(step > 0) selectFlag = 1;
5068 if(!selectFlag) Sweep(step);
5075 ChessSquare piece = boards[currentMove][toY][toX];
5078 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5079 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5080 if(!step) step = -1;
5081 } while(PieceToChar(pieceSweep) == '.');
5082 boards[currentMove][toY][toX] = pieceSweep;
5083 DrawPosition(FALSE, boards[currentMove]);
5084 boards[currentMove][toY][toX] = piece;
5086 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5088 AlphaRank(char *move, int n)
5090 // char *p = move, c; int x, y;
5092 if (appData.debugMode) {
5093 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5097 move[2]>='0' && move[2]<='9' &&
5098 move[3]>='a' && move[3]<='x' ) {
5100 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5101 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5103 if(move[0]>='0' && move[0]<='9' &&
5104 move[1]>='a' && move[1]<='x' &&
5105 move[2]>='0' && move[2]<='9' &&
5106 move[3]>='a' && move[3]<='x' ) {
5107 /* input move, Shogi -> normal */
5108 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5109 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5110 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5111 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5114 move[3]>='0' && move[3]<='9' &&
5115 move[2]>='a' && move[2]<='x' ) {
5117 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5118 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5121 move[0]>='a' && move[0]<='x' &&
5122 move[3]>='0' && move[3]<='9' &&
5123 move[2]>='a' && move[2]<='x' ) {
5124 /* output move, normal -> Shogi */
5125 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5126 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5127 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5128 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5129 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5131 if (appData.debugMode) {
5132 fprintf(debugFP, " out = '%s'\n", move);
5136 char yy_textstr[8000];
5138 /* Parser for moves from gnuchess, ICS, or user typein box */
5140 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5143 ChessMove *moveType;
5144 int *fromX, *fromY, *toX, *toY;
5147 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5149 switch (*moveType) {
5150 case WhitePromotion:
5151 case BlackPromotion:
5152 case WhiteNonPromotion:
5153 case BlackNonPromotion:
5155 case WhiteCapturesEnPassant:
5156 case BlackCapturesEnPassant:
5157 case WhiteKingSideCastle:
5158 case WhiteQueenSideCastle:
5159 case BlackKingSideCastle:
5160 case BlackQueenSideCastle:
5161 case WhiteKingSideCastleWild:
5162 case WhiteQueenSideCastleWild:
5163 case BlackKingSideCastleWild:
5164 case BlackQueenSideCastleWild:
5165 /* Code added by Tord: */
5166 case WhiteHSideCastleFR:
5167 case WhiteASideCastleFR:
5168 case BlackHSideCastleFR:
5169 case BlackASideCastleFR:
5170 /* End of code added by Tord */
5171 case IllegalMove: /* bug or odd chess variant */
5172 *fromX = currentMoveString[0] - AAA;
5173 *fromY = currentMoveString[1] - ONE;
5174 *toX = currentMoveString[2] - AAA;
5175 *toY = currentMoveString[3] - ONE;
5176 *promoChar = currentMoveString[4];
5177 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5178 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5179 if (appData.debugMode) {
5180 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5182 *fromX = *fromY = *toX = *toY = 0;
5185 if (appData.testLegality) {
5186 return (*moveType != IllegalMove);
5188 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5189 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5194 *fromX = *moveType == WhiteDrop ?
5195 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5196 (int) CharToPiece(ToLower(currentMoveString[0]));
5198 *toX = currentMoveString[2] - AAA;
5199 *toY = currentMoveString[3] - ONE;
5200 *promoChar = NULLCHAR;
5204 case ImpossibleMove:
5214 if (appData.debugMode) {
5215 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5218 *fromX = *fromY = *toX = *toY = 0;
5219 *promoChar = NULLCHAR;
5226 ParsePV(char *pv, Boolean storeComments)
5227 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5228 int fromX, fromY, toX, toY; char promoChar;
5233 endPV = forwardMostMove;
5235 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5236 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5237 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5238 if(appData.debugMode){
5239 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);
5241 if(!valid && nr == 0 &&
5242 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5243 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5244 // Hande case where played move is different from leading PV move
5245 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5246 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5247 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5248 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5249 endPV += 2; // if position different, keep this
5250 moveList[endPV-1][0] = fromX + AAA;
5251 moveList[endPV-1][1] = fromY + ONE;
5252 moveList[endPV-1][2] = toX + AAA;
5253 moveList[endPV-1][3] = toY + ONE;
5254 parseList[endPV-1][0] = NULLCHAR;
5255 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5258 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5259 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5260 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5261 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5262 valid++; // allow comments in PV
5266 if(endPV+1 > framePtr) break; // no space, truncate
5269 CopyBoard(boards[endPV], boards[endPV-1]);
5270 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5271 moveList[endPV-1][0] = fromX + AAA;
5272 moveList[endPV-1][1] = fromY + ONE;
5273 moveList[endPV-1][2] = toX + AAA;
5274 moveList[endPV-1][3] = toY + ONE;
5275 moveList[endPV-1][4] = promoChar;
5276 moveList[endPV-1][5] = NULLCHAR;
5277 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5279 CoordsToAlgebraic(boards[endPV - 1],
5280 PosFlags(endPV - 1),
5281 fromY, fromX, toY, toX, promoChar,
5282 parseList[endPV - 1]);
5284 parseList[endPV-1][0] = NULLCHAR;
5286 currentMove = endPV;
5287 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5288 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5289 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5290 DrawPosition(TRUE, boards[currentMove]);
5294 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5299 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5300 lastX = x; lastY = y;
5301 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5303 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5304 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5306 do{ while(buf[index] && buf[index] != '\n') index++;
5307 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5309 ParsePV(buf+startPV, FALSE);
5310 *start = startPV; *end = index-1;
5315 LoadPV(int x, int y)
5316 { // called on right mouse click to load PV
5317 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5318 lastX = x; lastY = y;
5319 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5326 if(endPV < 0) return;
5328 currentMove = forwardMostMove;
5329 ClearPremoveHighlights();
5330 DrawPosition(TRUE, boards[currentMove]);
5334 MovePV(int x, int y, int h)
5335 { // step through PV based on mouse coordinates (called on mouse move)
5336 int margin = h>>3, step = 0;
5338 // we must somehow check if right button is still down (might be released off board!)
5339 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5340 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5341 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5343 lastX = x; lastY = y;
5345 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5346 if(endPV < 0) return;
5347 if(y < margin) step = 1; else
5348 if(y > h - margin) step = -1;
5349 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5350 currentMove += step;
5351 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5352 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5353 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5354 DrawPosition(FALSE, boards[currentMove]);
5358 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5359 // All positions will have equal probability, but the current method will not provide a unique
5360 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5366 int piecesLeft[(int)BlackPawn];
5367 int seed, nrOfShuffles;
5369 void GetPositionNumber()
5370 { // sets global variable seed
5373 seed = appData.defaultFrcPosition;
5374 if(seed < 0) { // randomize based on time for negative FRC position numbers
5375 for(i=0; i<50; i++) seed += random();
5376 seed = random() ^ random() >> 8 ^ random() << 8;
5377 if(seed<0) seed = -seed;
5381 int put(Board board, int pieceType, int rank, int n, int shade)
5382 // put the piece on the (n-1)-th empty squares of the given shade
5386 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5387 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5388 board[rank][i] = (ChessSquare) pieceType;
5389 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5391 piecesLeft[pieceType]--;
5399 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5400 // calculate where the next piece goes, (any empty square), and put it there
5404 i = seed % squaresLeft[shade];
5405 nrOfShuffles *= squaresLeft[shade];
5406 seed /= squaresLeft[shade];
5407 put(board, pieceType, rank, i, shade);
5410 void AddTwoPieces(Board board, int pieceType, int rank)
5411 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5413 int i, n=squaresLeft[ANY], j=n-1, k;
5415 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5416 i = seed % k; // pick one
5419 while(i >= j) i -= j--;
5420 j = n - 1 - j; i += j;
5421 put(board, pieceType, rank, j, ANY);
5422 put(board, pieceType, rank, i, ANY);
5425 void SetUpShuffle(Board board, int number)
5429 GetPositionNumber(); nrOfShuffles = 1;
5431 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5432 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5433 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5435 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5437 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5438 p = (int) board[0][i];
5439 if(p < (int) BlackPawn) piecesLeft[p] ++;
5440 board[0][i] = EmptySquare;
5443 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5444 // shuffles restricted to allow normal castling put KRR first
5445 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5446 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5447 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5448 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5449 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5450 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5451 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5452 put(board, WhiteRook, 0, 0, ANY);
5453 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5456 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5457 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5458 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5459 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5460 while(piecesLeft[p] >= 2) {
5461 AddOnePiece(board, p, 0, LITE);
5462 AddOnePiece(board, p, 0, DARK);
5464 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5467 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5468 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5469 // but we leave King and Rooks for last, to possibly obey FRC restriction
5470 if(p == (int)WhiteRook) continue;
5471 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5472 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5475 // now everything is placed, except perhaps King (Unicorn) and Rooks
5477 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5478 // Last King gets castling rights
5479 while(piecesLeft[(int)WhiteUnicorn]) {
5480 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5481 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5484 while(piecesLeft[(int)WhiteKing]) {
5485 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5486 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5491 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5492 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5495 // Only Rooks can be left; simply place them all
5496 while(piecesLeft[(int)WhiteRook]) {
5497 i = put(board, WhiteRook, 0, 0, ANY);
5498 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5501 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5503 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5506 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5507 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5510 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5513 int SetCharTable( char *table, const char * map )
5514 /* [HGM] moved here from winboard.c because of its general usefulness */
5515 /* Basically a safe strcpy that uses the last character as King */
5517 int result = FALSE; int NrPieces;
5519 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5520 && NrPieces >= 12 && !(NrPieces&1)) {
5521 int i; /* [HGM] Accept even length from 12 to 34 */
5523 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5524 for( i=0; i<NrPieces/2-1; i++ ) {
5526 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5528 table[(int) WhiteKing] = map[NrPieces/2-1];
5529 table[(int) BlackKing] = map[NrPieces-1];
5537 void Prelude(Board board)
5538 { // [HGM] superchess: random selection of exo-pieces
5539 int i, j, k; ChessSquare p;
5540 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5542 GetPositionNumber(); // use FRC position number
5544 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5545 SetCharTable(pieceToChar, appData.pieceToCharTable);
5546 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5547 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5550 j = seed%4; seed /= 4;
5551 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5552 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5553 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5554 j = seed%3 + (seed%3 >= j); seed /= 3;
5555 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5556 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5557 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5558 j = seed%3; seed /= 3;
5559 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5560 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5561 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5562 j = seed%2 + (seed%2 >= j); seed /= 2;
5563 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5564 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5565 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5566 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5567 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5568 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5569 put(board, exoPieces[0], 0, 0, ANY);
5570 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5574 InitPosition(redraw)
5577 ChessSquare (* pieces)[BOARD_FILES];
5578 int i, j, pawnRow, overrule,
5579 oldx = gameInfo.boardWidth,
5580 oldy = gameInfo.boardHeight,
5581 oldh = gameInfo.holdingsWidth;
5584 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5586 /* [AS] Initialize pv info list [HGM] and game status */
5588 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5589 pvInfoList[i].depth = 0;
5590 boards[i][EP_STATUS] = EP_NONE;
5591 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5594 initialRulePlies = 0; /* 50-move counter start */
5596 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5597 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5601 /* [HGM] logic here is completely changed. In stead of full positions */
5602 /* the initialized data only consist of the two backranks. The switch */
5603 /* selects which one we will use, which is than copied to the Board */
5604 /* initialPosition, which for the rest is initialized by Pawns and */
5605 /* empty squares. This initial position is then copied to boards[0], */
5606 /* possibly after shuffling, so that it remains available. */
5608 gameInfo.holdingsWidth = 0; /* default board sizes */
5609 gameInfo.boardWidth = 8;
5610 gameInfo.boardHeight = 8;
5611 gameInfo.holdingsSize = 0;
5612 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5613 for(i=0; i<BOARD_FILES-2; i++)
5614 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5615 initialPosition[EP_STATUS] = EP_NONE;
5616 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5617 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5618 SetCharTable(pieceNickName, appData.pieceNickNames);
5619 else SetCharTable(pieceNickName, "............");
5622 switch (gameInfo.variant) {
5623 case VariantFischeRandom:
5624 shuffleOpenings = TRUE;
5627 case VariantShatranj:
5628 pieces = ShatranjArray;
5629 nrCastlingRights = 0;
5630 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5633 pieces = makrukArray;
5634 nrCastlingRights = 0;
5635 startedFromSetupPosition = TRUE;
5636 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5638 case VariantTwoKings:
5639 pieces = twoKingsArray;
5641 case VariantCapaRandom:
5642 shuffleOpenings = TRUE;
5643 case VariantCapablanca:
5644 pieces = CapablancaArray;
5645 gameInfo.boardWidth = 10;
5646 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5649 pieces = GothicArray;
5650 gameInfo.boardWidth = 10;
5651 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5654 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5655 gameInfo.holdingsSize = 7;
5658 pieces = JanusArray;
5659 gameInfo.boardWidth = 10;
5660 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5661 nrCastlingRights = 6;
5662 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5663 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5664 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5665 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5666 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5667 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5670 pieces = FalconArray;
5671 gameInfo.boardWidth = 10;
5672 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5674 case VariantXiangqi:
5675 pieces = XiangqiArray;
5676 gameInfo.boardWidth = 9;
5677 gameInfo.boardHeight = 10;
5678 nrCastlingRights = 0;
5679 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5682 pieces = ShogiArray;
5683 gameInfo.boardWidth = 9;
5684 gameInfo.boardHeight = 9;
5685 gameInfo.holdingsSize = 7;
5686 nrCastlingRights = 0;
5687 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5689 case VariantCourier:
5690 pieces = CourierArray;
5691 gameInfo.boardWidth = 12;
5692 nrCastlingRights = 0;
5693 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5695 case VariantKnightmate:
5696 pieces = KnightmateArray;
5697 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5699 case VariantSpartan:
5700 pieces = SpartanArray;
5701 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5704 pieces = fairyArray;
5705 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5708 pieces = GreatArray;
5709 gameInfo.boardWidth = 10;
5710 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5711 gameInfo.holdingsSize = 8;
5715 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5716 gameInfo.holdingsSize = 8;
5717 startedFromSetupPosition = TRUE;
5719 case VariantCrazyhouse:
5720 case VariantBughouse:
5722 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5723 gameInfo.holdingsSize = 5;
5725 case VariantWildCastle:
5727 /* !!?shuffle with kings guaranteed to be on d or e file */
5728 shuffleOpenings = 1;
5730 case VariantNoCastle:
5732 nrCastlingRights = 0;
5733 /* !!?unconstrained back-rank shuffle */
5734 shuffleOpenings = 1;
5739 if(appData.NrFiles >= 0) {
5740 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5741 gameInfo.boardWidth = appData.NrFiles;
5743 if(appData.NrRanks >= 0) {
5744 gameInfo.boardHeight = appData.NrRanks;
5746 if(appData.holdingsSize >= 0) {
5747 i = appData.holdingsSize;
5748 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5749 gameInfo.holdingsSize = i;
5751 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5752 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5753 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5755 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5756 if(pawnRow < 1) pawnRow = 1;
5757 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5759 /* User pieceToChar list overrules defaults */
5760 if(appData.pieceToCharTable != NULL)
5761 SetCharTable(pieceToChar, appData.pieceToCharTable);
5763 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5765 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5766 s = (ChessSquare) 0; /* account holding counts in guard band */
5767 for( i=0; i<BOARD_HEIGHT; i++ )
5768 initialPosition[i][j] = s;
5770 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5771 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5772 initialPosition[pawnRow][j] = WhitePawn;
5773 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5774 if(gameInfo.variant == VariantXiangqi) {
5776 initialPosition[pawnRow][j] =
5777 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5778 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5779 initialPosition[2][j] = WhiteCannon;
5780 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5784 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5786 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5789 initialPosition[1][j] = WhiteBishop;
5790 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5792 initialPosition[1][j] = WhiteRook;
5793 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5796 if( nrCastlingRights == -1) {
5797 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5798 /* This sets default castling rights from none to normal corners */
5799 /* Variants with other castling rights must set them themselves above */
5800 nrCastlingRights = 6;
5802 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5803 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5804 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5805 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5806 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5807 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5810 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5811 if(gameInfo.variant == VariantGreat) { // promotion commoners
5812 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5813 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5814 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5815 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5817 if( gameInfo.variant == VariantSChess ) {
5818 initialPosition[1][0] = BlackMarshall;
5819 initialPosition[2][0] = BlackAngel;
5820 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5821 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5822 initialPosition[1][1] = initialPosition[2][1] =
5823 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5825 if (appData.debugMode) {
5826 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5828 if(shuffleOpenings) {
5829 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5830 startedFromSetupPosition = TRUE;
5832 if(startedFromPositionFile) {
5833 /* [HGM] loadPos: use PositionFile for every new game */
5834 CopyBoard(initialPosition, filePosition);
5835 for(i=0; i<nrCastlingRights; i++)
5836 initialRights[i] = filePosition[CASTLING][i];
5837 startedFromSetupPosition = TRUE;
5840 CopyBoard(boards[0], initialPosition);
5842 if(oldx != gameInfo.boardWidth ||
5843 oldy != gameInfo.boardHeight ||
5844 oldv != gameInfo.variant ||
5845 oldh != gameInfo.holdingsWidth
5847 InitDrawingSizes(-2 ,0);
5849 oldv = gameInfo.variant;
5851 DrawPosition(TRUE, boards[currentMove]);
5855 SendBoard(cps, moveNum)
5856 ChessProgramState *cps;
5859 char message[MSG_SIZ];
5861 if (cps->useSetboard) {
5862 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5863 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5864 SendToProgram(message, cps);
5870 /* Kludge to set black to move, avoiding the troublesome and now
5871 * deprecated "black" command.
5873 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5874 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5876 SendToProgram("edit\n", cps);
5877 SendToProgram("#\n", cps);
5878 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5879 bp = &boards[moveNum][i][BOARD_LEFT];
5880 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5881 if ((int) *bp < (int) BlackPawn) {
5882 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5884 if(message[0] == '+' || message[0] == '~') {
5885 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5886 PieceToChar((ChessSquare)(DEMOTED *bp)),
5889 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5890 message[1] = BOARD_RGHT - 1 - j + '1';
5891 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5893 SendToProgram(message, cps);
5898 SendToProgram("c\n", cps);
5899 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5900 bp = &boards[moveNum][i][BOARD_LEFT];
5901 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5902 if (((int) *bp != (int) EmptySquare)
5903 && ((int) *bp >= (int) BlackPawn)) {
5904 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5906 if(message[0] == '+' || message[0] == '~') {
5907 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5908 PieceToChar((ChessSquare)(DEMOTED *bp)),
5911 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5912 message[1] = BOARD_RGHT - 1 - j + '1';
5913 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5915 SendToProgram(message, cps);
5920 SendToProgram(".\n", cps);
5922 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5926 DefaultPromoChoice(int white)
5929 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5930 result = WhiteFerz; // no choice
5931 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5932 result= WhiteKing; // in Suicide Q is the last thing we want
5933 else if(gameInfo.variant == VariantSpartan)
5934 result = white ? WhiteQueen : WhiteAngel;
5935 else result = WhiteQueen;
5936 if(!white) result = WHITE_TO_BLACK result;
5940 static int autoQueen; // [HGM] oneclick
5943 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5945 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5946 /* [HGM] add Shogi promotions */
5947 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5952 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5953 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5955 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5956 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5959 piece = boards[currentMove][fromY][fromX];
5960 if(gameInfo.variant == VariantShogi) {
5961 promotionZoneSize = BOARD_HEIGHT/3;
5962 highestPromotingPiece = (int)WhiteFerz;
5963 } else if(gameInfo.variant == VariantMakruk) {
5964 promotionZoneSize = 3;
5967 // Treat Lance as Pawn when it is not representing Amazon
5968 if(gameInfo.variant != VariantSuper) {
5969 if(piece == WhiteLance) piece = WhitePawn; else
5970 if(piece == BlackLance) piece = BlackPawn;
5973 // next weed out all moves that do not touch the promotion zone at all
5974 if((int)piece >= BlackPawn) {
5975 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5977 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5979 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5980 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5983 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5985 // weed out mandatory Shogi promotions
5986 if(gameInfo.variant == VariantShogi) {
5987 if(piece >= BlackPawn) {
5988 if(toY == 0 && piece == BlackPawn ||
5989 toY == 0 && piece == BlackQueen ||
5990 toY <= 1 && piece == BlackKnight) {
5995 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5996 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5997 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6004 // weed out obviously illegal Pawn moves
6005 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6006 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6007 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6008 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6009 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6010 // note we are not allowed to test for valid (non-)capture, due to premove
6013 // we either have a choice what to promote to, or (in Shogi) whether to promote
6014 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6015 *promoChoice = PieceToChar(BlackFerz); // no choice
6018 // no sense asking what we must promote to if it is going to explode...
6019 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6020 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6023 // give caller the default choice even if we will not make it
6024 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6025 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6026 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6027 && gameInfo.variant != VariantShogi
6028 && gameInfo.variant != VariantSuper) return FALSE;
6029 if(autoQueen) return FALSE; // predetermined
6031 // suppress promotion popup on illegal moves that are not premoves
6032 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6033 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6034 if(appData.testLegality && !premove) {
6035 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6036 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6037 if(moveType != WhitePromotion && moveType != BlackPromotion)
6045 InPalace(row, column)
6047 { /* [HGM] for Xiangqi */
6048 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6049 column < (BOARD_WIDTH + 4)/2 &&
6050 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6055 PieceForSquare (x, y)
6059 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6062 return boards[currentMove][y][x];
6066 OKToStartUserMove(x, y)
6069 ChessSquare from_piece;
6072 if (matchMode) return FALSE;
6073 if (gameMode == EditPosition) return TRUE;
6075 if (x >= 0 && y >= 0)
6076 from_piece = boards[currentMove][y][x];
6078 from_piece = EmptySquare;
6080 if (from_piece == EmptySquare) return FALSE;
6082 white_piece = (int)from_piece >= (int)WhitePawn &&
6083 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6086 case PlayFromGameFile:
6088 case TwoMachinesPlay:
6096 case MachinePlaysWhite:
6097 case IcsPlayingBlack:
6098 if (appData.zippyPlay) return FALSE;
6100 DisplayMoveError(_("You are playing Black"));
6105 case MachinePlaysBlack:
6106 case IcsPlayingWhite:
6107 if (appData.zippyPlay) return FALSE;
6109 DisplayMoveError(_("You are playing White"));
6115 if (!white_piece && WhiteOnMove(currentMove)) {
6116 DisplayMoveError(_("It is White's turn"));
6119 if (white_piece && !WhiteOnMove(currentMove)) {
6120 DisplayMoveError(_("It is Black's turn"));
6123 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6124 /* Editing correspondence game history */
6125 /* Could disallow this or prompt for confirmation */
6130 case BeginningOfGame:
6131 if (appData.icsActive) return FALSE;
6132 if (!appData.noChessProgram) {
6134 DisplayMoveError(_("You are playing White"));
6141 if (!white_piece && WhiteOnMove(currentMove)) {
6142 DisplayMoveError(_("It is White's turn"));
6145 if (white_piece && !WhiteOnMove(currentMove)) {
6146 DisplayMoveError(_("It is Black's turn"));
6155 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6156 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6157 && gameMode != AnalyzeFile && gameMode != Training) {
6158 DisplayMoveError(_("Displayed position is not current"));
6165 OnlyMove(int *x, int *y, Boolean captures) {
6166 DisambiguateClosure cl;
6167 if (appData.zippyPlay) return FALSE;
6169 case MachinePlaysBlack:
6170 case IcsPlayingWhite:
6171 case BeginningOfGame:
6172 if(!WhiteOnMove(currentMove)) return FALSE;
6174 case MachinePlaysWhite:
6175 case IcsPlayingBlack:
6176 if(WhiteOnMove(currentMove)) return FALSE;
6183 cl.pieceIn = EmptySquare;
6188 cl.promoCharIn = NULLCHAR;
6189 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6190 if( cl.kind == NormalMove ||
6191 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6192 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6193 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6200 if(cl.kind != ImpossibleMove) return FALSE;
6201 cl.pieceIn = EmptySquare;
6206 cl.promoCharIn = NULLCHAR;
6207 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6208 if( cl.kind == NormalMove ||
6209 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6210 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6211 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6216 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6222 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6223 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6224 int lastLoadGameUseList = FALSE;
6225 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6226 ChessMove lastLoadGameStart = EndOfFile;
6229 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6230 int fromX, fromY, toX, toY;
6234 ChessSquare pdown, pup;
6236 /* Check if the user is playing in turn. This is complicated because we
6237 let the user "pick up" a piece before it is his turn. So the piece he
6238 tried to pick up may have been captured by the time he puts it down!
6239 Therefore we use the color the user is supposed to be playing in this
6240 test, not the color of the piece that is currently on the starting
6241 square---except in EditGame mode, where the user is playing both
6242 sides; fortunately there the capture race can't happen. (It can
6243 now happen in IcsExamining mode, but that's just too bad. The user
6244 will get a somewhat confusing message in that case.)
6248 case PlayFromGameFile:
6250 case TwoMachinesPlay:
6254 /* We switched into a game mode where moves are not accepted,
6255 perhaps while the mouse button was down. */
6258 case MachinePlaysWhite:
6259 /* User is moving for Black */
6260 if (WhiteOnMove(currentMove)) {
6261 DisplayMoveError(_("It is White's turn"));
6266 case MachinePlaysBlack:
6267 /* User is moving for White */
6268 if (!WhiteOnMove(currentMove)) {
6269 DisplayMoveError(_("It is Black's turn"));
6276 case BeginningOfGame:
6279 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6280 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6281 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6282 /* User is moving for Black */
6283 if (WhiteOnMove(currentMove)) {
6284 DisplayMoveError(_("It is White's turn"));
6288 /* User is moving for White */
6289 if (!WhiteOnMove(currentMove)) {
6290 DisplayMoveError(_("It is Black's turn"));
6296 case IcsPlayingBlack:
6297 /* User is moving for Black */
6298 if (WhiteOnMove(currentMove)) {
6299 if (!appData.premove) {
6300 DisplayMoveError(_("It is White's turn"));
6301 } else if (toX >= 0 && toY >= 0) {
6304 premoveFromX = fromX;
6305 premoveFromY = fromY;
6306 premovePromoChar = promoChar;
6308 if (appData.debugMode)
6309 fprintf(debugFP, "Got premove: fromX %d,"
6310 "fromY %d, toX %d, toY %d\n",
6311 fromX, fromY, toX, toY);
6317 case IcsPlayingWhite:
6318 /* User is moving for White */
6319 if (!WhiteOnMove(currentMove)) {
6320 if (!appData.premove) {
6321 DisplayMoveError(_("It is Black's turn"));
6322 } else if (toX >= 0 && toY >= 0) {
6325 premoveFromX = fromX;
6326 premoveFromY = fromY;
6327 premovePromoChar = promoChar;
6329 if (appData.debugMode)
6330 fprintf(debugFP, "Got premove: fromX %d,"
6331 "fromY %d, toX %d, toY %d\n",
6332 fromX, fromY, toX, toY);
6342 /* EditPosition, empty square, or different color piece;
6343 click-click move is possible */
6344 if (toX == -2 || toY == -2) {
6345 boards[0][fromY][fromX] = EmptySquare;
6346 DrawPosition(FALSE, boards[currentMove]);
6348 } else if (toX >= 0 && toY >= 0) {
6349 boards[0][toY][toX] = boards[0][fromY][fromX];
6350 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6351 if(boards[0][fromY][0] != EmptySquare) {
6352 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6353 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6356 if(fromX == BOARD_RGHT+1) {
6357 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6358 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6359 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6362 boards[0][fromY][fromX] = EmptySquare;
6363 DrawPosition(FALSE, boards[currentMove]);
6369 if(toX < 0 || toY < 0) return;
6370 pdown = boards[currentMove][fromY][fromX];
6371 pup = boards[currentMove][toY][toX];
6373 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6374 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6375 if( pup != EmptySquare ) return;
6376 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6377 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6378 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6379 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6380 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6381 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6382 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6386 /* [HGM] always test for legality, to get promotion info */
6387 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6388 fromY, fromX, toY, toX, promoChar);
6389 /* [HGM] but possibly ignore an IllegalMove result */
6390 if (appData.testLegality) {
6391 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6392 DisplayMoveError(_("Illegal move"));
6397 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6400 /* Common tail of UserMoveEvent and DropMenuEvent */
6402 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6404 int fromX, fromY, toX, toY;
6405 /*char*/int promoChar;
6409 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6410 // [HGM] superchess: suppress promotions to non-available piece
6411 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6412 if(WhiteOnMove(currentMove)) {
6413 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6415 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6419 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6420 move type in caller when we know the move is a legal promotion */
6421 if(moveType == NormalMove && promoChar)
6422 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6424 /* [HGM] <popupFix> The following if has been moved here from
6425 UserMoveEvent(). Because it seemed to belong here (why not allow
6426 piece drops in training games?), and because it can only be
6427 performed after it is known to what we promote. */
6428 if (gameMode == Training) {
6429 /* compare the move played on the board to the next move in the
6430 * game. If they match, display the move and the opponent's response.
6431 * If they don't match, display an error message.
6435 CopyBoard(testBoard, boards[currentMove]);
6436 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6438 if (CompareBoards(testBoard, boards[currentMove+1])) {
6439 ForwardInner(currentMove+1);
6441 /* Autoplay the opponent's response.
6442 * if appData.animate was TRUE when Training mode was entered,
6443 * the response will be animated.
6445 saveAnimate = appData.animate;
6446 appData.animate = animateTraining;
6447 ForwardInner(currentMove+1);
6448 appData.animate = saveAnimate;
6450 /* check for the end of the game */
6451 if (currentMove >= forwardMostMove) {
6452 gameMode = PlayFromGameFile;
6454 SetTrainingModeOff();
6455 DisplayInformation(_("End of game"));
6458 DisplayError(_("Incorrect move"), 0);
6463 /* Ok, now we know that the move is good, so we can kill
6464 the previous line in Analysis Mode */
6465 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6466 && currentMove < forwardMostMove) {
6467 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6468 else forwardMostMove = currentMove;
6471 /* If we need the chess program but it's dead, restart it */
6472 ResurrectChessProgram();
6474 /* A user move restarts a paused game*/
6478 thinkOutput[0] = NULLCHAR;
6480 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6482 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6483 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6487 if (gameMode == BeginningOfGame) {
6488 if (appData.noChessProgram) {
6489 gameMode = EditGame;
6493 gameMode = MachinePlaysBlack;
6496 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6498 if (first.sendName) {
6499 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6500 SendToProgram(buf, &first);
6507 /* Relay move to ICS or chess engine */
6508 if (appData.icsActive) {
6509 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6510 gameMode == IcsExamining) {
6511 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6512 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6514 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6516 // also send plain move, in case ICS does not understand atomic claims
6517 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6521 if (first.sendTime && (gameMode == BeginningOfGame ||
6522 gameMode == MachinePlaysWhite ||
6523 gameMode == MachinePlaysBlack)) {
6524 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6526 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6527 // [HGM] book: if program might be playing, let it use book
6528 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6529 first.maybeThinking = TRUE;
6530 } else SendMoveToProgram(forwardMostMove-1, &first);
6531 if (currentMove == cmailOldMove + 1) {
6532 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6536 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540 if(appData.testLegality)
6541 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6547 if (WhiteOnMove(currentMove)) {
6548 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6550 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6554 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6559 case MachinePlaysBlack:
6560 case MachinePlaysWhite:
6561 /* disable certain menu options while machine is thinking */
6562 SetMachineThinkingEnables();
6569 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6570 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6572 if(bookHit) { // [HGM] book: simulate book reply
6573 static char bookMove[MSG_SIZ]; // a bit generous?
6575 programStats.nodes = programStats.depth = programStats.time =
6576 programStats.score = programStats.got_only_move = 0;
6577 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6579 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6580 strcat(bookMove, bookHit);
6581 HandleMachineMove(bookMove, &first);
6587 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6594 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6595 Markers *m = (Markers *) closure;
6596 if(rf == fromY && ff == fromX)
6597 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6598 || kind == WhiteCapturesEnPassant
6599 || kind == BlackCapturesEnPassant);
6600 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6604 MarkTargetSquares(int clear)
6607 if(!appData.markers || !appData.highlightDragging ||
6608 !appData.testLegality || gameMode == EditPosition) return;
6610 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6613 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6614 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6615 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6617 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6620 DrawPosition(TRUE, NULL);
6624 Explode(Board board, int fromX, int fromY, int toX, int toY)
6626 if(gameInfo.variant == VariantAtomic &&
6627 (board[toY][toX] != EmptySquare || // capture?
6628 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6629 board[fromY][fromX] == BlackPawn )
6631 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6637 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6639 int CanPromote(ChessSquare piece, int y)
6641 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6642 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6643 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6644 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6645 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6646 gameInfo.variant == VariantMakruk) return FALSE;
6647 return (piece == BlackPawn && y == 1 ||
6648 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6649 piece == BlackLance && y == 1 ||
6650 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6653 void LeftClick(ClickType clickType, int xPix, int yPix)
6656 Boolean saveAnimate;
6657 static int second = 0, promotionChoice = 0, clearFlag = 0;
6658 char promoChoice = NULLCHAR;
6661 if(appData.seekGraph && appData.icsActive && loggedOn &&
6662 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6663 SeekGraphClick(clickType, xPix, yPix, 0);
6667 if (clickType == Press) ErrorPopDown();
6668 MarkTargetSquares(1);
6670 x = EventToSquare(xPix, BOARD_WIDTH);
6671 y = EventToSquare(yPix, BOARD_HEIGHT);
6672 if (!flipView && y >= 0) {
6673 y = BOARD_HEIGHT - 1 - y;
6675 if (flipView && x >= 0) {
6676 x = BOARD_WIDTH - 1 - x;
6679 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6680 defaultPromoChoice = promoSweep;
6681 promoSweep = EmptySquare; // terminate sweep
6682 promoDefaultAltered = TRUE;
6683 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6686 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6687 if(clickType == Release) return; // ignore upclick of click-click destination
6688 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6689 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6690 if(gameInfo.holdingsWidth &&
6691 (WhiteOnMove(currentMove)
6692 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6693 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6694 // click in right holdings, for determining promotion piece
6695 ChessSquare p = boards[currentMove][y][x];
6696 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6697 if(p != EmptySquare) {
6698 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6703 DrawPosition(FALSE, boards[currentMove]);
6707 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6708 if(clickType == Press
6709 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6710 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6711 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6714 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6715 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6717 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6718 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6719 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6720 defaultPromoChoice = DefaultPromoChoice(side);
6723 autoQueen = appData.alwaysPromoteToQueen;
6727 gatingPiece = EmptySquare;
6728 if (clickType != Press) {
6729 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6730 DragPieceEnd(xPix, yPix); dragging = 0;
6731 DrawPosition(FALSE, NULL);
6735 fromX = x; fromY = y;
6736 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6737 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6738 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6740 if (OKToStartUserMove(fromX, fromY)) {
6742 MarkTargetSquares(0);
6743 DragPieceBegin(xPix, yPix); dragging = 1;
6744 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6745 promoSweep = defaultPromoChoice;
6746 selectFlag = 0; lastX = xPix; lastY = yPix;
6747 Sweep(0); // Pawn that is going to promote: preview promotion piece
6748 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6750 if (appData.highlightDragging) {
6751 SetHighlights(fromX, fromY, -1, -1);
6753 } else fromX = fromY = -1;
6759 if (clickType == Press && gameMode != EditPosition) {
6764 // ignore off-board to clicks
6765 if(y < 0 || x < 0) return;
6767 /* Check if clicking again on the same color piece */
6768 fromP = boards[currentMove][fromY][fromX];
6769 toP = boards[currentMove][y][x];
6770 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6771 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6772 WhitePawn <= toP && toP <= WhiteKing &&
6773 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6774 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6775 (BlackPawn <= fromP && fromP <= BlackKing &&
6776 BlackPawn <= toP && toP <= BlackKing &&
6777 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6778 !(fromP == BlackKing && toP == BlackRook && frc))) {
6779 /* Clicked again on same color piece -- changed his mind */
6780 second = (x == fromX && y == fromY);
6781 promoDefaultAltered = FALSE;
6782 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6783 if (appData.highlightDragging) {
6784 SetHighlights(x, y, -1, -1);
6788 if (OKToStartUserMove(x, y)) {
6789 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6790 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6791 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6792 gatingPiece = boards[currentMove][fromY][fromX];
6793 else gatingPiece = EmptySquare;
6795 fromY = y; dragging = 1;
6796 MarkTargetSquares(0);
6797 DragPieceBegin(xPix, yPix);
6798 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6799 promoSweep = defaultPromoChoice;
6800 selectFlag = 0; lastX = xPix; lastY = yPix;
6801 Sweep(0); // Pawn that is going to promote: preview promotion piece
6805 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6808 // ignore clicks on holdings
6809 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6812 if (clickType == Release && x == fromX && y == fromY) {
6813 DragPieceEnd(xPix, yPix); dragging = 0;
6815 // a deferred attempt to click-click move an empty square on top of a piece
6816 boards[currentMove][y][x] = EmptySquare;
6818 DrawPosition(FALSE, boards[currentMove]);
6819 fromX = fromY = -1; clearFlag = 0;
6822 if (appData.animateDragging) {
6823 /* Undo animation damage if any */
6824 DrawPosition(FALSE, NULL);
6827 /* Second up/down in same square; just abort move */
6830 gatingPiece = EmptySquare;
6833 ClearPremoveHighlights();
6835 /* First upclick in same square; start click-click mode */
6836 SetHighlights(x, y, -1, -1);
6843 /* we now have a different from- and (possibly off-board) to-square */
6844 /* Completed move */
6847 saveAnimate = appData.animate;
6848 if (clickType == Press) {
6849 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6850 // must be Edit Position mode with empty-square selected
6851 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6852 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6855 /* Finish clickclick move */
6856 if (appData.animate || appData.highlightLastMove) {
6857 SetHighlights(fromX, fromY, toX, toY);
6862 /* Finish drag move */
6863 if (appData.highlightLastMove) {
6864 SetHighlights(fromX, fromY, toX, toY);
6868 DragPieceEnd(xPix, yPix); dragging = 0;
6869 /* Don't animate move and drag both */
6870 appData.animate = FALSE;
6873 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6874 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6875 ChessSquare piece = boards[currentMove][fromY][fromX];
6876 if(gameMode == EditPosition && piece != EmptySquare &&
6877 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6880 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6881 n = PieceToNumber(piece - (int)BlackPawn);
6882 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6883 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6884 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6886 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6887 n = PieceToNumber(piece);
6888 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6889 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6890 boards[currentMove][n][BOARD_WIDTH-2]++;
6892 boards[currentMove][fromY][fromX] = EmptySquare;
6896 DrawPosition(TRUE, boards[currentMove]);
6900 // off-board moves should not be highlighted
6901 if(x < 0 || y < 0) ClearHighlights();
6903 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6905 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6906 SetHighlights(fromX, fromY, toX, toY);
6907 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6908 // [HGM] super: promotion to captured piece selected from holdings
6909 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6910 promotionChoice = TRUE;
6911 // kludge follows to temporarily execute move on display, without promoting yet
6912 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6913 boards[currentMove][toY][toX] = p;
6914 DrawPosition(FALSE, boards[currentMove]);
6915 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6916 boards[currentMove][toY][toX] = q;
6917 DisplayMessage("Click in holdings to choose piece", "");
6922 int oldMove = currentMove;
6923 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6924 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6925 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6926 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6927 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6928 DrawPosition(TRUE, boards[currentMove]);
6931 appData.animate = saveAnimate;
6932 if (appData.animate || appData.animateDragging) {
6933 /* Undo animation damage if needed */
6934 DrawPosition(FALSE, NULL);
6938 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6939 { // front-end-free part taken out of PieceMenuPopup
6940 int whichMenu; int xSqr, ySqr;
6942 if(seekGraphUp) { // [HGM] seekgraph
6943 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6944 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6948 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6949 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6950 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6951 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6952 if(action == Press) {
6953 originalFlip = flipView;
6954 flipView = !flipView; // temporarily flip board to see game from partners perspective
6955 DrawPosition(TRUE, partnerBoard);
6956 DisplayMessage(partnerStatus, "");
6958 } else if(action == Release) {
6959 flipView = originalFlip;
6960 DrawPosition(TRUE, boards[currentMove]);
6966 xSqr = EventToSquare(x, BOARD_WIDTH);
6967 ySqr = EventToSquare(y, BOARD_HEIGHT);
6968 if (action == Release) {
6969 if(pieceSweep != EmptySquare) {
6970 EditPositionMenuEvent(pieceSweep, toX, toY);
6971 pieceSweep = EmptySquare;
6972 } else UnLoadPV(); // [HGM] pv
6974 if (action != Press) return -2; // return code to be ignored
6977 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6979 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6980 if (xSqr < 0 || ySqr < 0) return -1;
6981 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6982 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6983 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6984 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6988 if(!appData.icsEngineAnalyze) return -1;
6989 case IcsPlayingWhite:
6990 case IcsPlayingBlack:
6991 if(!appData.zippyPlay) goto noZip;
6994 case MachinePlaysWhite:
6995 case MachinePlaysBlack:
6996 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6997 if (!appData.dropMenu) {
6999 return 2; // flag front-end to grab mouse events
7001 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7002 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7005 if (xSqr < 0 || ySqr < 0) return -1;
7006 if (!appData.dropMenu || appData.testLegality &&
7007 gameInfo.variant != VariantBughouse &&
7008 gameInfo.variant != VariantCrazyhouse) return -1;
7009 whichMenu = 1; // drop menu
7015 if (((*fromX = xSqr) < 0) ||
7016 ((*fromY = ySqr) < 0)) {
7017 *fromX = *fromY = -1;
7021 *fromX = BOARD_WIDTH - 1 - *fromX;
7023 *fromY = BOARD_HEIGHT - 1 - *fromY;
7028 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7030 // char * hint = lastHint;
7031 FrontEndProgramStats stats;
7033 stats.which = cps == &first ? 0 : 1;
7034 stats.depth = cpstats->depth;
7035 stats.nodes = cpstats->nodes;
7036 stats.score = cpstats->score;
7037 stats.time = cpstats->time;
7038 stats.pv = cpstats->movelist;
7039 stats.hint = lastHint;
7040 stats.an_move_index = 0;
7041 stats.an_move_count = 0;
7043 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7044 stats.hint = cpstats->move_name;
7045 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7046 stats.an_move_count = cpstats->nr_moves;
7049 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
7051 SetProgramStats( &stats );
7054 #define MAXPLAYERS 500
7057 TourneyStandings(int display)
7059 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7060 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7061 char result, *p, *names[MAXPLAYERS];
7063 names[0] = p = strdup(appData.participants);
7064 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7066 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7068 while(result = appData.results[nr]) {
7069 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7070 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7071 wScore = bScore = 0;
7073 case '+': wScore = 2; break;
7074 case '-': bScore = 2; break;
7075 case '=': wScore = bScore = 1; break;
7077 case '*': return NULL; // tourney not finished
7085 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7086 for(w=0; w<nPlayers; w++) {
7088 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7089 ranking[w] = b; points[w] = bScore; score[b] = -2;
7091 p = malloc(nPlayers*34+1);
7092 for(w=0; w<nPlayers && w<display; w++)
7093 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7099 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7100 { // count all piece types
7102 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7103 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7104 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7107 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7108 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7109 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7110 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7111 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7112 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7117 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7119 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7120 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7122 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7123 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7124 if(myPawns == 2 && nMine == 3) // KPP
7125 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7126 if(myPawns == 1 && nMine == 2) // KP
7127 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7128 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7129 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7130 if(myPawns) return FALSE;
7131 if(pCnt[WhiteRook+side])
7132 return pCnt[BlackRook-side] ||
7133 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7134 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7135 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7136 if(pCnt[WhiteCannon+side]) {
7137 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7138 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7140 if(pCnt[WhiteKnight+side])
7141 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7146 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7148 VariantClass v = gameInfo.variant;
7150 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7151 if(v == VariantShatranj) return TRUE; // always winnable through baring
7152 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7153 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7155 if(v == VariantXiangqi) {
7156 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7158 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7159 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7160 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7161 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7162 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7163 if(stale) // we have at least one last-rank P plus perhaps C
7164 return majors // KPKX
7165 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7167 return pCnt[WhiteFerz+side] // KCAK
7168 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7169 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7170 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7172 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7173 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7175 if(nMine == 1) return FALSE; // bare King
7176 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
7177 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7178 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7179 // by now we have King + 1 piece (or multiple Bishops on the same color)
7180 if(pCnt[WhiteKnight+side])
7181 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7182 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7183 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7185 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7186 if(pCnt[WhiteAlfil+side])
7187 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7188 if(pCnt[WhiteWazir+side])
7189 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7196 Adjudicate(ChessProgramState *cps)
7197 { // [HGM] some adjudications useful with buggy engines
7198 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7199 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7200 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7201 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7202 int k, count = 0; static int bare = 1;
7203 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7204 Boolean canAdjudicate = !appData.icsActive;
7206 // most tests only when we understand the game, i.e. legality-checking on
7207 if( appData.testLegality )
7208 { /* [HGM] Some more adjudications for obstinate engines */
7209 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7210 static int moveCount = 6;
7212 char *reason = NULL;
7214 /* Count what is on board. */
7215 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7217 /* Some material-based adjudications that have to be made before stalemate test */
7218 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7219 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7220 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7221 if(canAdjudicate && appData.checkMates) {
7223 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7224 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7225 "Xboard adjudication: King destroyed", GE_XBOARD );
7230 /* Bare King in Shatranj (loses) or Losers (wins) */
7231 if( nrW == 1 || nrB == 1) {
7232 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7233 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7234 if(canAdjudicate && appData.checkMates) {
7236 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7237 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7238 "Xboard adjudication: Bare king", GE_XBOARD );
7242 if( gameInfo.variant == VariantShatranj && --bare < 0)
7244 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7245 if(canAdjudicate && appData.checkMates) {
7246 /* but only adjudicate if adjudication enabled */
7248 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7249 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7250 "Xboard adjudication: Bare king", GE_XBOARD );
7257 // don't wait for engine to announce game end if we can judge ourselves
7258 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7260 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7261 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7262 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7263 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7266 reason = "Xboard adjudication: 3rd check";
7267 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7277 reason = "Xboard adjudication: Stalemate";
7278 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7279 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7280 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7281 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7282 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7283 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7284 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7285 EP_CHECKMATE : EP_WINS);
7286 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7287 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7291 reason = "Xboard adjudication: Checkmate";
7292 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7296 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7298 result = GameIsDrawn; break;
7300 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7302 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7306 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7308 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7309 GameEnds( result, reason, GE_XBOARD );
7313 /* Next absolutely insufficient mating material. */
7314 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7315 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7316 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7318 /* always flag draws, for judging claims */
7319 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7321 if(canAdjudicate && appData.materialDraws) {
7322 /* but only adjudicate them if adjudication enabled */
7323 if(engineOpponent) {
7324 SendToProgram("force\n", engineOpponent); // suppress reply
7325 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7327 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7332 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7333 if(gameInfo.variant == VariantXiangqi ?
7334 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7336 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7337 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7338 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7339 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7341 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7342 { /* if the first 3 moves do not show a tactical win, declare draw */
7343 if(engineOpponent) {
7344 SendToProgram("force\n", engineOpponent); // suppress reply
7345 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7347 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7350 } else moveCount = 6;
7352 if (appData.debugMode) { int i;
7353 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7354 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7355 appData.drawRepeats);
7356 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7357 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7361 // Repetition draws and 50-move rule can be applied independently of legality testing
7363 /* Check for rep-draws */
7365 for(k = forwardMostMove-2;
7366 k>=backwardMostMove && k>=forwardMostMove-100 &&
7367 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7368 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7371 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7372 /* compare castling rights */
7373 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7374 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7375 rights++; /* King lost rights, while rook still had them */
7376 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7377 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7378 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7379 rights++; /* but at least one rook lost them */
7381 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7382 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7384 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7385 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7386 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7389 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7390 && appData.drawRepeats > 1) {
7391 /* adjudicate after user-specified nr of repeats */
7392 int result = GameIsDrawn;
7393 char *details = "XBoard adjudication: repetition draw";
7394 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7395 // [HGM] xiangqi: check for forbidden perpetuals
7396 int m, ourPerpetual = 1, hisPerpetual = 1;
7397 for(m=forwardMostMove; m>k; m-=2) {
7398 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7399 ourPerpetual = 0; // the current mover did not always check
7400 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7401 hisPerpetual = 0; // the opponent did not always check
7403 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7404 ourPerpetual, hisPerpetual);
7405 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7406 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7407 details = "Xboard adjudication: perpetual checking";
7409 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7410 break; // (or we would have caught him before). Abort repetition-checking loop.
7412 // Now check for perpetual chases
7413 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7414 hisPerpetual = PerpetualChase(k, forwardMostMove);
7415 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7416 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7417 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7418 details = "Xboard adjudication: perpetual chasing";
7420 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7421 break; // Abort repetition-checking loop.
7423 // if neither of us is checking or chasing all the time, or both are, it is draw
7425 if(engineOpponent) {
7426 SendToProgram("force\n", engineOpponent); // suppress reply
7427 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7429 GameEnds( result, details, GE_XBOARD );
7432 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7433 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7437 /* Now we test for 50-move draws. Determine ply count */
7438 count = forwardMostMove;
7439 /* look for last irreversble move */
7440 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7442 /* if we hit starting position, add initial plies */
7443 if( count == backwardMostMove )
7444 count -= initialRulePlies;
7445 count = forwardMostMove - count;
7446 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7447 // adjust reversible move counter for checks in Xiangqi
7448 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7449 if(i < backwardMostMove) i = backwardMostMove;
7450 while(i <= forwardMostMove) {
7451 lastCheck = inCheck; // check evasion does not count
7452 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7453 if(inCheck || lastCheck) count--; // check does not count
7458 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7459 /* this is used to judge if draw claims are legal */
7460 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7461 if(engineOpponent) {
7462 SendToProgram("force\n", engineOpponent); // suppress reply
7463 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7465 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7469 /* if draw offer is pending, treat it as a draw claim
7470 * when draw condition present, to allow engines a way to
7471 * claim draws before making their move to avoid a race
7472 * condition occurring after their move
7474 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7476 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7477 p = "Draw claim: 50-move rule";
7478 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7479 p = "Draw claim: 3-fold repetition";
7480 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7481 p = "Draw claim: insufficient mating material";
7482 if( p != NULL && canAdjudicate) {
7483 if(engineOpponent) {
7484 SendToProgram("force\n", engineOpponent); // suppress reply
7485 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7487 GameEnds( GameIsDrawn, p, GE_XBOARD );
7492 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7493 if(engineOpponent) {
7494 SendToProgram("force\n", engineOpponent); // suppress reply
7495 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7497 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7503 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7504 { // [HGM] book: this routine intercepts moves to simulate book replies
7505 char *bookHit = NULL;
7507 //first determine if the incoming move brings opponent into his book
7508 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7509 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7510 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7511 if(bookHit != NULL && !cps->bookSuspend) {
7512 // make sure opponent is not going to reply after receiving move to book position
7513 SendToProgram("force\n", cps);
7514 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7516 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7517 // now arrange restart after book miss
7519 // after a book hit we never send 'go', and the code after the call to this routine
7520 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7522 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7523 SendToProgram(buf, cps);
7524 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7525 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7526 SendToProgram("go\n", cps);
7527 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7528 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7529 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7530 SendToProgram("go\n", cps);
7531 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7533 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7537 ChessProgramState *savedState;
7538 void DeferredBookMove(void)
7540 if(savedState->lastPing != savedState->lastPong)
7541 ScheduleDelayedEvent(DeferredBookMove, 10);
7543 HandleMachineMove(savedMessage, savedState);
7547 HandleMachineMove(message, cps)
7549 ChessProgramState *cps;
7551 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7552 char realname[MSG_SIZ];
7553 int fromX, fromY, toX, toY;
7562 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7564 * Kludge to ignore BEL characters
7566 while (*message == '\007') message++;
7569 * [HGM] engine debug message: ignore lines starting with '#' character
7571 if(cps->debug && *message == '#') return;
7574 * Look for book output
7576 if (cps == &first && bookRequested) {
7577 if (message[0] == '\t' || message[0] == ' ') {
7578 /* Part of the book output is here; append it */
7579 strcat(bookOutput, message);
7580 strcat(bookOutput, " \n");
7582 } else if (bookOutput[0] != NULLCHAR) {
7583 /* All of book output has arrived; display it */
7584 char *p = bookOutput;
7585 while (*p != NULLCHAR) {
7586 if (*p == '\t') *p = ' ';
7589 DisplayInformation(bookOutput);
7590 bookRequested = FALSE;
7591 /* Fall through to parse the current output */
7596 * Look for machine move.
7598 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7599 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7601 /* This method is only useful on engines that support ping */
7602 if (cps->lastPing != cps->lastPong) {
7603 if (gameMode == BeginningOfGame) {
7604 /* Extra move from before last new; ignore */
7605 if (appData.debugMode) {
7606 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7609 if (appData.debugMode) {
7610 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7611 cps->which, gameMode);
7614 SendToProgram("undo\n", cps);
7620 case BeginningOfGame:
7621 /* Extra move from before last reset; ignore */
7622 if (appData.debugMode) {
7623 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7630 /* Extra move after we tried to stop. The mode test is
7631 not a reliable way of detecting this problem, but it's
7632 the best we can do on engines that don't support ping.
7634 if (appData.debugMode) {
7635 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7636 cps->which, gameMode);
7638 SendToProgram("undo\n", cps);
7641 case MachinePlaysWhite:
7642 case IcsPlayingWhite:
7643 machineWhite = TRUE;
7646 case MachinePlaysBlack:
7647 case IcsPlayingBlack:
7648 machineWhite = FALSE;
7651 case TwoMachinesPlay:
7652 machineWhite = (cps->twoMachinesColor[0] == 'w');
7655 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7656 if (appData.debugMode) {
7658 "Ignoring move out of turn by %s, gameMode %d"
7659 ", forwardMost %d\n",
7660 cps->which, gameMode, forwardMostMove);
7665 if (appData.debugMode) { int f = forwardMostMove;
7666 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7667 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7668 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7670 if(cps->alphaRank) AlphaRank(machineMove, 4);
7671 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7672 &fromX, &fromY, &toX, &toY, &promoChar)) {
7673 /* Machine move could not be parsed; ignore it. */
7674 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7675 machineMove, _(cps->which));
7676 DisplayError(buf1, 0);
7677 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7678 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7679 if (gameMode == TwoMachinesPlay) {
7680 GameEnds(machineWhite ? BlackWins : WhiteWins,
7686 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7687 /* So we have to redo legality test with true e.p. status here, */
7688 /* to make sure an illegal e.p. capture does not slip through, */
7689 /* to cause a forfeit on a justified illegal-move complaint */
7690 /* of the opponent. */
7691 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7693 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7694 fromY, fromX, toY, toX, promoChar);
7695 if (appData.debugMode) {
7697 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7698 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7699 fprintf(debugFP, "castling rights\n");
7701 if(moveType == IllegalMove) {
7702 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7703 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7704 GameEnds(machineWhite ? BlackWins : WhiteWins,
7707 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7708 /* [HGM] Kludge to handle engines that send FRC-style castling
7709 when they shouldn't (like TSCP-Gothic) */
7711 case WhiteASideCastleFR:
7712 case BlackASideCastleFR:
7714 currentMoveString[2]++;
7716 case WhiteHSideCastleFR:
7717 case BlackHSideCastleFR:
7719 currentMoveString[2]--;
7721 default: ; // nothing to do, but suppresses warning of pedantic compilers
7724 hintRequested = FALSE;
7725 lastHint[0] = NULLCHAR;
7726 bookRequested = FALSE;
7727 /* Program may be pondering now */
7728 cps->maybeThinking = TRUE;
7729 if (cps->sendTime == 2) cps->sendTime = 1;
7730 if (cps->offeredDraw) cps->offeredDraw--;
7732 /* [AS] Save move info*/
7733 pvInfoList[ forwardMostMove ].score = programStats.score;
7734 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7735 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7737 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7739 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7740 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7743 while( count < adjudicateLossPlies ) {
7744 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7747 score = -score; /* Flip score for winning side */
7750 if( score > adjudicateLossThreshold ) {
7757 if( count >= adjudicateLossPlies ) {
7758 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7760 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7761 "Xboard adjudication",
7768 if(Adjudicate(cps)) {
7769 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7770 return; // [HGM] adjudicate: for all automatic game ends
7774 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7776 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7777 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7779 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7781 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7783 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7784 char buf[3*MSG_SIZ];
7786 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7787 programStats.score / 100.,
7789 programStats.time / 100.,
7790 (unsigned int)programStats.nodes,
7791 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7792 programStats.movelist);
7794 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7799 /* [AS] Clear stats for next move */
7800 ClearProgramStats();
7801 thinkOutput[0] = NULLCHAR;
7802 hiddenThinkOutputState = 0;
7805 if (gameMode == TwoMachinesPlay) {
7806 /* [HGM] relaying draw offers moved to after reception of move */
7807 /* and interpreting offer as claim if it brings draw condition */
7808 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7809 SendToProgram("draw\n", cps->other);
7811 if (cps->other->sendTime) {
7812 SendTimeRemaining(cps->other,
7813 cps->other->twoMachinesColor[0] == 'w');
7815 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7816 if (firstMove && !bookHit) {
7818 if (cps->other->useColors) {
7819 SendToProgram(cps->other->twoMachinesColor, cps->other);
7821 SendToProgram("go\n", cps->other);
7823 cps->other->maybeThinking = TRUE;
7826 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7828 if (!pausing && appData.ringBellAfterMoves) {
7833 * Reenable menu items that were disabled while
7834 * machine was thinking
7836 if (gameMode != TwoMachinesPlay)
7837 SetUserThinkingEnables();
7839 // [HGM] book: after book hit opponent has received move and is now in force mode
7840 // force the book reply into it, and then fake that it outputted this move by jumping
7841 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7843 static char bookMove[MSG_SIZ]; // a bit generous?
7845 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7846 strcat(bookMove, bookHit);
7849 programStats.nodes = programStats.depth = programStats.time =
7850 programStats.score = programStats.got_only_move = 0;
7851 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7853 if(cps->lastPing != cps->lastPong) {
7854 savedMessage = message; // args for deferred call
7856 ScheduleDelayedEvent(DeferredBookMove, 10);
7865 /* Set special modes for chess engines. Later something general
7866 * could be added here; for now there is just one kludge feature,
7867 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7868 * when "xboard" is given as an interactive command.
7870 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7871 cps->useSigint = FALSE;
7872 cps->useSigterm = FALSE;
7874 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7875 ParseFeatures(message+8, cps);
7876 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7879 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7880 int dummy, s=6; char buf[MSG_SIZ];
7881 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7882 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7883 ParseFEN(boards[0], &dummy, message+s);
7884 DrawPosition(TRUE, boards[0]);
7885 startedFromSetupPosition = TRUE;
7888 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7889 * want this, I was asked to put it in, and obliged.
7891 if (!strncmp(message, "setboard ", 9)) {
7892 Board initial_position;
7894 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7896 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7897 DisplayError(_("Bad FEN received from engine"), 0);
7901 CopyBoard(boards[0], initial_position);
7902 initialRulePlies = FENrulePlies;
7903 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7904 else gameMode = MachinePlaysBlack;
7905 DrawPosition(FALSE, boards[currentMove]);
7911 * Look for communication commands
7913 if (!strncmp(message, "telluser ", 9)) {
7914 if(message[9] == '\\' && message[10] == '\\')
7915 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7916 DisplayNote(message + 9);
7919 if (!strncmp(message, "tellusererror ", 14)) {
7921 if(message[14] == '\\' && message[15] == '\\')
7922 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7923 DisplayError(message + 14, 0);
7926 if (!strncmp(message, "tellopponent ", 13)) {
7927 if (appData.icsActive) {
7929 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7933 DisplayNote(message + 13);
7937 if (!strncmp(message, "tellothers ", 11)) {
7938 if (appData.icsActive) {
7940 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7946 if (!strncmp(message, "tellall ", 8)) {
7947 if (appData.icsActive) {
7949 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7953 DisplayNote(message + 8);
7957 if (strncmp(message, "warning", 7) == 0) {
7958 /* Undocumented feature, use tellusererror in new code */
7959 DisplayError(message, 0);
7962 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7963 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7964 strcat(realname, " query");
7965 AskQuestion(realname, buf2, buf1, cps->pr);
7968 /* Commands from the engine directly to ICS. We don't allow these to be
7969 * sent until we are logged on. Crafty kibitzes have been known to
7970 * interfere with the login process.
7973 if (!strncmp(message, "tellics ", 8)) {
7974 SendToICS(message + 8);
7978 if (!strncmp(message, "tellicsnoalias ", 15)) {
7979 SendToICS(ics_prefix);
7980 SendToICS(message + 15);
7984 /* The following are for backward compatibility only */
7985 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7986 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7987 SendToICS(ics_prefix);
7993 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7997 * If the move is illegal, cancel it and redraw the board.
7998 * Also deal with other error cases. Matching is rather loose
7999 * here to accommodate engines written before the spec.
8001 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8002 strncmp(message, "Error", 5) == 0) {
8003 if (StrStr(message, "name") ||
8004 StrStr(message, "rating") || StrStr(message, "?") ||
8005 StrStr(message, "result") || StrStr(message, "board") ||
8006 StrStr(message, "bk") || StrStr(message, "computer") ||
8007 StrStr(message, "variant") || StrStr(message, "hint") ||
8008 StrStr(message, "random") || StrStr(message, "depth") ||
8009 StrStr(message, "accepted")) {
8012 if (StrStr(message, "protover")) {
8013 /* Program is responding to input, so it's apparently done
8014 initializing, and this error message indicates it is
8015 protocol version 1. So we don't need to wait any longer
8016 for it to initialize and send feature commands. */
8017 FeatureDone(cps, 1);
8018 cps->protocolVersion = 1;
8021 cps->maybeThinking = FALSE;
8023 if (StrStr(message, "draw")) {
8024 /* Program doesn't have "draw" command */
8025 cps->sendDrawOffers = 0;
8028 if (cps->sendTime != 1 &&
8029 (StrStr(message, "time") || StrStr(message, "otim"))) {
8030 /* Program apparently doesn't have "time" or "otim" command */
8034 if (StrStr(message, "analyze")) {
8035 cps->analysisSupport = FALSE;
8036 cps->analyzing = FALSE;
8038 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8039 DisplayError(buf2, 0);
8042 if (StrStr(message, "(no matching move)st")) {
8043 /* Special kludge for GNU Chess 4 only */
8044 cps->stKludge = TRUE;
8045 SendTimeControl(cps, movesPerSession, timeControl,
8046 timeIncrement, appData.searchDepth,
8050 if (StrStr(message, "(no matching move)sd")) {
8051 /* Special kludge for GNU Chess 4 only */
8052 cps->sdKludge = TRUE;
8053 SendTimeControl(cps, movesPerSession, timeControl,
8054 timeIncrement, appData.searchDepth,
8058 if (!StrStr(message, "llegal")) {
8061 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8062 gameMode == IcsIdle) return;
8063 if (forwardMostMove <= backwardMostMove) return;
8064 if (pausing) PauseEvent();
8065 if(appData.forceIllegal) {
8066 // [HGM] illegal: machine refused move; force position after move into it
8067 SendToProgram("force\n", cps);
8068 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8069 // we have a real problem now, as SendBoard will use the a2a3 kludge
8070 // when black is to move, while there might be nothing on a2 or black
8071 // might already have the move. So send the board as if white has the move.
8072 // But first we must change the stm of the engine, as it refused the last move
8073 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8074 if(WhiteOnMove(forwardMostMove)) {
8075 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8076 SendBoard(cps, forwardMostMove); // kludgeless board
8078 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8079 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8080 SendBoard(cps, forwardMostMove+1); // kludgeless board
8082 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8083 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8084 gameMode == TwoMachinesPlay)
8085 SendToProgram("go\n", cps);
8088 if (gameMode == PlayFromGameFile) {
8089 /* Stop reading this game file */
8090 gameMode = EditGame;
8093 /* [HGM] illegal-move claim should forfeit game when Xboard */
8094 /* only passes fully legal moves */
8095 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8096 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8097 "False illegal-move claim", GE_XBOARD );
8098 return; // do not take back move we tested as valid
8100 currentMove = forwardMostMove-1;
8101 DisplayMove(currentMove-1); /* before DisplayMoveError */
8102 SwitchClocks(forwardMostMove-1); // [HGM] race
8103 DisplayBothClocks();
8104 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8105 parseList[currentMove], _(cps->which));
8106 DisplayMoveError(buf1);
8107 DrawPosition(FALSE, boards[currentMove]);
8110 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8111 /* Program has a broken "time" command that
8112 outputs a string not ending in newline.
8118 * If chess program startup fails, exit with an error message.
8119 * Attempts to recover here are futile.
8121 if ((StrStr(message, "unknown host") != NULL)
8122 || (StrStr(message, "No remote directory") != NULL)
8123 || (StrStr(message, "not found") != NULL)
8124 || (StrStr(message, "No such file") != NULL)
8125 || (StrStr(message, "can't alloc") != NULL)
8126 || (StrStr(message, "Permission denied") != NULL)) {
8128 cps->maybeThinking = FALSE;
8129 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8130 _(cps->which), cps->program, cps->host, message);
8131 RemoveInputSource(cps->isr);
8132 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8133 if(cps == &first) appData.noChessProgram = TRUE;
8134 DisplayError(buf1, 0);
8140 * Look for hint output
8142 if (sscanf(message, "Hint: %s", buf1) == 1) {
8143 if (cps == &first && hintRequested) {
8144 hintRequested = FALSE;
8145 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8146 &fromX, &fromY, &toX, &toY, &promoChar)) {
8147 (void) CoordsToAlgebraic(boards[forwardMostMove],
8148 PosFlags(forwardMostMove),
8149 fromY, fromX, toY, toX, promoChar, buf1);
8150 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8151 DisplayInformation(buf2);
8153 /* Hint move could not be parsed!? */
8154 snprintf(buf2, sizeof(buf2),
8155 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8156 buf1, _(cps->which));
8157 DisplayError(buf2, 0);
8160 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8166 * Ignore other messages if game is not in progress
8168 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8169 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8172 * look for win, lose, draw, or draw offer
8174 if (strncmp(message, "1-0", 3) == 0) {
8175 char *p, *q, *r = "";
8176 p = strchr(message, '{');
8184 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8186 } else if (strncmp(message, "0-1", 3) == 0) {
8187 char *p, *q, *r = "";
8188 p = strchr(message, '{');
8196 /* Kludge for Arasan 4.1 bug */
8197 if (strcmp(r, "Black resigns") == 0) {
8198 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8201 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8203 } else if (strncmp(message, "1/2", 3) == 0) {
8204 char *p, *q, *r = "";
8205 p = strchr(message, '{');
8214 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8217 } else if (strncmp(message, "White resign", 12) == 0) {
8218 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8220 } else if (strncmp(message, "Black resign", 12) == 0) {
8221 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8223 } else if (strncmp(message, "White matches", 13) == 0 ||
8224 strncmp(message, "Black matches", 13) == 0 ) {
8225 /* [HGM] ignore GNUShogi noises */
8227 } else if (strncmp(message, "White", 5) == 0 &&
8228 message[5] != '(' &&
8229 StrStr(message, "Black") == NULL) {
8230 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8232 } else if (strncmp(message, "Black", 5) == 0 &&
8233 message[5] != '(') {
8234 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8236 } else if (strcmp(message, "resign") == 0 ||
8237 strcmp(message, "computer resigns") == 0) {
8239 case MachinePlaysBlack:
8240 case IcsPlayingBlack:
8241 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8243 case MachinePlaysWhite:
8244 case IcsPlayingWhite:
8245 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8247 case TwoMachinesPlay:
8248 if (cps->twoMachinesColor[0] == 'w')
8249 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8251 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8258 } else if (strncmp(message, "opponent mates", 14) == 0) {
8260 case MachinePlaysBlack:
8261 case IcsPlayingBlack:
8262 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8264 case MachinePlaysWhite:
8265 case IcsPlayingWhite:
8266 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8268 case TwoMachinesPlay:
8269 if (cps->twoMachinesColor[0] == 'w')
8270 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8272 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8279 } else if (strncmp(message, "computer mates", 14) == 0) {
8281 case MachinePlaysBlack:
8282 case IcsPlayingBlack:
8283 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8285 case MachinePlaysWhite:
8286 case IcsPlayingWhite:
8287 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8289 case TwoMachinesPlay:
8290 if (cps->twoMachinesColor[0] == 'w')
8291 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8293 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8300 } else if (strncmp(message, "checkmate", 9) == 0) {
8301 if (WhiteOnMove(forwardMostMove)) {
8302 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8304 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8307 } else if (strstr(message, "Draw") != NULL ||
8308 strstr(message, "game is a draw") != NULL) {
8309 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8311 } else if (strstr(message, "offer") != NULL &&
8312 strstr(message, "draw") != NULL) {
8314 if (appData.zippyPlay && first.initDone) {
8315 /* Relay offer to ICS */
8316 SendToICS(ics_prefix);
8317 SendToICS("draw\n");
8320 cps->offeredDraw = 2; /* valid until this engine moves twice */
8321 if (gameMode == TwoMachinesPlay) {
8322 if (cps->other->offeredDraw) {
8323 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8324 /* [HGM] in two-machine mode we delay relaying draw offer */
8325 /* until after we also have move, to see if it is really claim */
8327 } else if (gameMode == MachinePlaysWhite ||
8328 gameMode == MachinePlaysBlack) {
8329 if (userOfferedDraw) {
8330 DisplayInformation(_("Machine accepts your draw offer"));
8331 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8333 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8340 * Look for thinking output
8342 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8343 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8345 int plylev, mvleft, mvtot, curscore, time;
8346 char mvname[MOVE_LEN];
8350 int prefixHint = FALSE;
8351 mvname[0] = NULLCHAR;
8354 case MachinePlaysBlack:
8355 case IcsPlayingBlack:
8356 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8358 case MachinePlaysWhite:
8359 case IcsPlayingWhite:
8360 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8365 case IcsObserving: /* [DM] icsEngineAnalyze */
8366 if (!appData.icsEngineAnalyze) ignore = TRUE;
8368 case TwoMachinesPlay:
8369 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8379 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8381 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8382 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8384 if (plyext != ' ' && plyext != '\t') {
8388 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8389 if( cps->scoreIsAbsolute &&
8390 ( gameMode == MachinePlaysBlack ||
8391 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8392 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8393 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8394 !WhiteOnMove(currentMove)
8397 curscore = -curscore;
8401 tempStats.depth = plylev;
8402 tempStats.nodes = nodes;
8403 tempStats.time = time;
8404 tempStats.score = curscore;
8405 tempStats.got_only_move = 0;
8407 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8410 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8411 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8412 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8413 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8414 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8415 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8416 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8417 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8420 /* Buffer overflow protection */
8421 if (buf1[0] != NULLCHAR) {
8422 if (strlen(buf1) >= sizeof(tempStats.movelist)
8423 && appData.debugMode) {
8425 "PV is too long; using the first %u bytes.\n",
8426 (unsigned) sizeof(tempStats.movelist) - 1);
8429 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8431 sprintf(tempStats.movelist, " no PV\n");
8434 if (tempStats.seen_stat) {
8435 tempStats.ok_to_send = 1;
8438 if (strchr(tempStats.movelist, '(') != NULL) {
8439 tempStats.line_is_book = 1;
8440 tempStats.nr_moves = 0;
8441 tempStats.moves_left = 0;
8443 tempStats.line_is_book = 0;
8446 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8447 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8449 SendProgramStatsToFrontend( cps, &tempStats );
8452 [AS] Protect the thinkOutput buffer from overflow... this
8453 is only useful if buf1 hasn't overflowed first!
8455 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8457 (gameMode == TwoMachinesPlay ?
8458 ToUpper(cps->twoMachinesColor[0]) : ' '),
8459 ((double) curscore) / 100.0,
8460 prefixHint ? lastHint : "",
8461 prefixHint ? " " : "" );
8463 if( buf1[0] != NULLCHAR ) {
8464 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8466 if( strlen(buf1) > max_len ) {
8467 if( appData.debugMode) {
8468 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8470 buf1[max_len+1] = '\0';
8473 strcat( thinkOutput, buf1 );
8476 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8477 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8478 DisplayMove(currentMove - 1);
8482 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8483 /* crafty (9.25+) says "(only move) <move>"
8484 * if there is only 1 legal move
8486 sscanf(p, "(only move) %s", buf1);
8487 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8488 sprintf(programStats.movelist, "%s (only move)", buf1);
8489 programStats.depth = 1;
8490 programStats.nr_moves = 1;
8491 programStats.moves_left = 1;
8492 programStats.nodes = 1;
8493 programStats.time = 1;
8494 programStats.got_only_move = 1;
8496 /* Not really, but we also use this member to
8497 mean "line isn't going to change" (Crafty
8498 isn't searching, so stats won't change) */
8499 programStats.line_is_book = 1;
8501 SendProgramStatsToFrontend( cps, &programStats );
8503 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8504 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8505 DisplayMove(currentMove - 1);
8508 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8509 &time, &nodes, &plylev, &mvleft,
8510 &mvtot, mvname) >= 5) {
8511 /* The stat01: line is from Crafty (9.29+) in response
8512 to the "." command */
8513 programStats.seen_stat = 1;
8514 cps->maybeThinking = TRUE;
8516 if (programStats.got_only_move || !appData.periodicUpdates)
8519 programStats.depth = plylev;
8520 programStats.time = time;
8521 programStats.nodes = nodes;
8522 programStats.moves_left = mvleft;
8523 programStats.nr_moves = mvtot;
8524 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8525 programStats.ok_to_send = 1;
8526 programStats.movelist[0] = '\0';
8528 SendProgramStatsToFrontend( cps, &programStats );
8532 } else if (strncmp(message,"++",2) == 0) {
8533 /* Crafty 9.29+ outputs this */
8534 programStats.got_fail = 2;
8537 } else if (strncmp(message,"--",2) == 0) {
8538 /* Crafty 9.29+ outputs this */
8539 programStats.got_fail = 1;
8542 } else if (thinkOutput[0] != NULLCHAR &&
8543 strncmp(message, " ", 4) == 0) {
8544 unsigned message_len;
8547 while (*p && *p == ' ') p++;
8549 message_len = strlen( p );
8551 /* [AS] Avoid buffer overflow */
8552 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8553 strcat(thinkOutput, " ");
8554 strcat(thinkOutput, p);
8557 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8558 strcat(programStats.movelist, " ");
8559 strcat(programStats.movelist, p);
8562 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8563 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8564 DisplayMove(currentMove - 1);
8572 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8573 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8575 ChessProgramStats cpstats;
8577 if (plyext != ' ' && plyext != '\t') {
8581 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8582 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8583 curscore = -curscore;
8586 cpstats.depth = plylev;
8587 cpstats.nodes = nodes;
8588 cpstats.time = time;
8589 cpstats.score = curscore;
8590 cpstats.got_only_move = 0;
8591 cpstats.movelist[0] = '\0';
8593 if (buf1[0] != NULLCHAR) {
8594 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8597 cpstats.ok_to_send = 0;
8598 cpstats.line_is_book = 0;
8599 cpstats.nr_moves = 0;
8600 cpstats.moves_left = 0;
8602 SendProgramStatsToFrontend( cps, &cpstats );
8609 /* Parse a game score from the character string "game", and
8610 record it as the history of the current game. The game
8611 score is NOT assumed to start from the standard position.
8612 The display is not updated in any way.
8615 ParseGameHistory(game)
8619 int fromX, fromY, toX, toY, boardIndex;
8624 if (appData.debugMode)
8625 fprintf(debugFP, "Parsing game history: %s\n", game);
8627 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8628 gameInfo.site = StrSave(appData.icsHost);
8629 gameInfo.date = PGNDate();
8630 gameInfo.round = StrSave("-");
8632 /* Parse out names of players */
8633 while (*game == ' ') game++;
8635 while (*game != ' ') *p++ = *game++;
8637 gameInfo.white = StrSave(buf);
8638 while (*game == ' ') game++;
8640 while (*game != ' ' && *game != '\n') *p++ = *game++;
8642 gameInfo.black = StrSave(buf);
8645 boardIndex = blackPlaysFirst ? 1 : 0;
8648 yyboardindex = boardIndex;
8649 moveType = (ChessMove) Myylex();
8651 case IllegalMove: /* maybe suicide chess, etc. */
8652 if (appData.debugMode) {
8653 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8654 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8655 setbuf(debugFP, NULL);
8657 case WhitePromotion:
8658 case BlackPromotion:
8659 case WhiteNonPromotion:
8660 case BlackNonPromotion:
8662 case WhiteCapturesEnPassant:
8663 case BlackCapturesEnPassant:
8664 case WhiteKingSideCastle:
8665 case WhiteQueenSideCastle:
8666 case BlackKingSideCastle:
8667 case BlackQueenSideCastle:
8668 case WhiteKingSideCastleWild:
8669 case WhiteQueenSideCastleWild:
8670 case BlackKingSideCastleWild:
8671 case BlackQueenSideCastleWild:
8673 case WhiteHSideCastleFR:
8674 case WhiteASideCastleFR:
8675 case BlackHSideCastleFR:
8676 case BlackASideCastleFR:
8678 fromX = currentMoveString[0] - AAA;
8679 fromY = currentMoveString[1] - ONE;
8680 toX = currentMoveString[2] - AAA;
8681 toY = currentMoveString[3] - ONE;
8682 promoChar = currentMoveString[4];
8686 fromX = moveType == WhiteDrop ?
8687 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8688 (int) CharToPiece(ToLower(currentMoveString[0]));
8690 toX = currentMoveString[2] - AAA;
8691 toY = currentMoveString[3] - ONE;
8692 promoChar = NULLCHAR;
8696 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8697 if (appData.debugMode) {
8698 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8699 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8700 setbuf(debugFP, NULL);
8702 DisplayError(buf, 0);
8704 case ImpossibleMove:
8706 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8707 if (appData.debugMode) {
8708 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8709 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8710 setbuf(debugFP, NULL);
8712 DisplayError(buf, 0);
8715 if (boardIndex < backwardMostMove) {
8716 /* Oops, gap. How did that happen? */
8717 DisplayError(_("Gap in move list"), 0);
8720 backwardMostMove = blackPlaysFirst ? 1 : 0;
8721 if (boardIndex > forwardMostMove) {
8722 forwardMostMove = boardIndex;
8726 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8727 strcat(parseList[boardIndex-1], " ");
8728 strcat(parseList[boardIndex-1], yy_text);
8740 case GameUnfinished:
8741 if (gameMode == IcsExamining) {
8742 if (boardIndex < backwardMostMove) {
8743 /* Oops, gap. How did that happen? */
8746 backwardMostMove = blackPlaysFirst ? 1 : 0;
8749 gameInfo.result = moveType;
8750 p = strchr(yy_text, '{');
8751 if (p == NULL) p = strchr(yy_text, '(');
8754 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8756 q = strchr(p, *p == '{' ? '}' : ')');
8757 if (q != NULL) *q = NULLCHAR;
8760 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8761 gameInfo.resultDetails = StrSave(p);
8764 if (boardIndex >= forwardMostMove &&
8765 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8766 backwardMostMove = blackPlaysFirst ? 1 : 0;
8769 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8770 fromY, fromX, toY, toX, promoChar,
8771 parseList[boardIndex]);
8772 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8773 /* currentMoveString is set as a side-effect of yylex */
8774 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8775 strcat(moveList[boardIndex], "\n");
8777 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8778 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8784 if(gameInfo.variant != VariantShogi)
8785 strcat(parseList[boardIndex - 1], "+");
8789 strcat(parseList[boardIndex - 1], "#");
8796 /* Apply a move to the given board */
8798 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8799 int fromX, fromY, toX, toY;
8803 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8804 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8806 /* [HGM] compute & store e.p. status and castling rights for new position */
8807 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8809 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8810 oldEP = (signed char)board[EP_STATUS];
8811 board[EP_STATUS] = EP_NONE;
8813 if( board[toY][toX] != EmptySquare )
8814 board[EP_STATUS] = EP_CAPTURE;
8816 if (fromY == DROP_RANK) {
8818 piece = board[toY][toX] = (ChessSquare) fromX;
8822 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8823 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8824 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8826 if( board[fromY][fromX] == WhitePawn ) {
8827 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8828 board[EP_STATUS] = EP_PAWN_MOVE;
8830 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8831 gameInfo.variant != VariantBerolina || toX < fromX)
8832 board[EP_STATUS] = toX | berolina;
8833 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8834 gameInfo.variant != VariantBerolina || toX > fromX)
8835 board[EP_STATUS] = toX;
8838 if( board[fromY][fromX] == BlackPawn ) {
8839 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8840 board[EP_STATUS] = EP_PAWN_MOVE;
8841 if( toY-fromY== -2) {
8842 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8843 gameInfo.variant != VariantBerolina || toX < fromX)
8844 board[EP_STATUS] = toX | berolina;
8845 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8846 gameInfo.variant != VariantBerolina || toX > fromX)
8847 board[EP_STATUS] = toX;
8851 for(i=0; i<nrCastlingRights; i++) {
8852 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8853 board[CASTLING][i] == toX && castlingRank[i] == toY
8854 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8857 if (fromX == toX && fromY == toY) return;
8859 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8860 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8861 if(gameInfo.variant == VariantKnightmate)
8862 king += (int) WhiteUnicorn - (int) WhiteKing;
8864 /* Code added by Tord: */
8865 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8866 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8867 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8868 board[fromY][fromX] = EmptySquare;
8869 board[toY][toX] = EmptySquare;
8870 if((toX > fromX) != (piece == WhiteRook)) {
8871 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8873 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8875 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8876 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8877 board[fromY][fromX] = EmptySquare;
8878 board[toY][toX] = EmptySquare;
8879 if((toX > fromX) != (piece == BlackRook)) {
8880 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8882 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8884 /* End of code added by Tord */
8886 } else if (board[fromY][fromX] == king
8887 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8888 && toY == fromY && toX > fromX+1) {
8889 board[fromY][fromX] = EmptySquare;
8890 board[toY][toX] = king;
8891 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8892 board[fromY][BOARD_RGHT-1] = EmptySquare;
8893 } else if (board[fromY][fromX] == king
8894 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8895 && toY == fromY && toX < fromX-1) {
8896 board[fromY][fromX] = EmptySquare;
8897 board[toY][toX] = king;
8898 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8899 board[fromY][BOARD_LEFT] = EmptySquare;
8900 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8901 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8902 && toY >= BOARD_HEIGHT-promoRank
8904 /* white pawn promotion */
8905 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8906 if (board[toY][toX] == EmptySquare) {
8907 board[toY][toX] = WhiteQueen;
8909 if(gameInfo.variant==VariantBughouse ||
8910 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8911 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8912 board[fromY][fromX] = EmptySquare;
8913 } else if ((fromY == BOARD_HEIGHT-4)
8915 && gameInfo.variant != VariantXiangqi
8916 && gameInfo.variant != VariantBerolina
8917 && (board[fromY][fromX] == WhitePawn)
8918 && (board[toY][toX] == EmptySquare)) {
8919 board[fromY][fromX] = EmptySquare;
8920 board[toY][toX] = WhitePawn;
8921 captured = board[toY - 1][toX];
8922 board[toY - 1][toX] = EmptySquare;
8923 } else if ((fromY == BOARD_HEIGHT-4)
8925 && gameInfo.variant == VariantBerolina
8926 && (board[fromY][fromX] == WhitePawn)
8927 && (board[toY][toX] == EmptySquare)) {
8928 board[fromY][fromX] = EmptySquare;
8929 board[toY][toX] = WhitePawn;
8930 if(oldEP & EP_BEROLIN_A) {
8931 captured = board[fromY][fromX-1];
8932 board[fromY][fromX-1] = EmptySquare;
8933 }else{ captured = board[fromY][fromX+1];
8934 board[fromY][fromX+1] = EmptySquare;
8936 } else if (board[fromY][fromX] == king
8937 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8938 && toY == fromY && toX > fromX+1) {
8939 board[fromY][fromX] = EmptySquare;
8940 board[toY][toX] = king;
8941 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8942 board[fromY][BOARD_RGHT-1] = EmptySquare;
8943 } else if (board[fromY][fromX] == king
8944 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8945 && toY == fromY && toX < fromX-1) {
8946 board[fromY][fromX] = EmptySquare;
8947 board[toY][toX] = king;
8948 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8949 board[fromY][BOARD_LEFT] = EmptySquare;
8950 } else if (fromY == 7 && fromX == 3
8951 && board[fromY][fromX] == BlackKing
8952 && toY == 7 && toX == 5) {
8953 board[fromY][fromX] = EmptySquare;
8954 board[toY][toX] = BlackKing;
8955 board[fromY][7] = EmptySquare;
8956 board[toY][4] = BlackRook;
8957 } else if (fromY == 7 && fromX == 3
8958 && board[fromY][fromX] == BlackKing
8959 && toY == 7 && toX == 1) {
8960 board[fromY][fromX] = EmptySquare;
8961 board[toY][toX] = BlackKing;
8962 board[fromY][0] = EmptySquare;
8963 board[toY][2] = BlackRook;
8964 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8965 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8968 /* black pawn promotion */
8969 board[toY][toX] = CharToPiece(ToLower(promoChar));
8970 if (board[toY][toX] == EmptySquare) {
8971 board[toY][toX] = BlackQueen;
8973 if(gameInfo.variant==VariantBughouse ||
8974 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8975 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8976 board[fromY][fromX] = EmptySquare;
8977 } else if ((fromY == 3)
8979 && gameInfo.variant != VariantXiangqi
8980 && gameInfo.variant != VariantBerolina
8981 && (board[fromY][fromX] == BlackPawn)
8982 && (board[toY][toX] == EmptySquare)) {
8983 board[fromY][fromX] = EmptySquare;
8984 board[toY][toX] = BlackPawn;
8985 captured = board[toY + 1][toX];
8986 board[toY + 1][toX] = EmptySquare;
8987 } else if ((fromY == 3)
8989 && gameInfo.variant == VariantBerolina
8990 && (board[fromY][fromX] == BlackPawn)
8991 && (board[toY][toX] == EmptySquare)) {
8992 board[fromY][fromX] = EmptySquare;
8993 board[toY][toX] = BlackPawn;
8994 if(oldEP & EP_BEROLIN_A) {
8995 captured = board[fromY][fromX-1];
8996 board[fromY][fromX-1] = EmptySquare;
8997 }else{ captured = board[fromY][fromX+1];
8998 board[fromY][fromX+1] = EmptySquare;
9001 board[toY][toX] = board[fromY][fromX];
9002 board[fromY][fromX] = EmptySquare;
9006 if (gameInfo.holdingsWidth != 0) {
9008 /* !!A lot more code needs to be written to support holdings */
9009 /* [HGM] OK, so I have written it. Holdings are stored in the */
9010 /* penultimate board files, so they are automaticlly stored */
9011 /* in the game history. */
9012 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9013 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9014 /* Delete from holdings, by decreasing count */
9015 /* and erasing image if necessary */
9016 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9017 if(p < (int) BlackPawn) { /* white drop */
9018 p -= (int)WhitePawn;
9019 p = PieceToNumber((ChessSquare)p);
9020 if(p >= gameInfo.holdingsSize) p = 0;
9021 if(--board[p][BOARD_WIDTH-2] <= 0)
9022 board[p][BOARD_WIDTH-1] = EmptySquare;
9023 if((int)board[p][BOARD_WIDTH-2] < 0)
9024 board[p][BOARD_WIDTH-2] = 0;
9025 } else { /* black drop */
9026 p -= (int)BlackPawn;
9027 p = PieceToNumber((ChessSquare)p);
9028 if(p >= gameInfo.holdingsSize) p = 0;
9029 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9030 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9031 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9032 board[BOARD_HEIGHT-1-p][1] = 0;
9035 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9036 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9037 /* [HGM] holdings: Add to holdings, if holdings exist */
9038 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9039 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9040 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9043 if (p >= (int) BlackPawn) {
9044 p -= (int)BlackPawn;
9045 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9046 /* in Shogi restore piece to its original first */
9047 captured = (ChessSquare) (DEMOTED captured);
9050 p = PieceToNumber((ChessSquare)p);
9051 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9052 board[p][BOARD_WIDTH-2]++;
9053 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9055 p -= (int)WhitePawn;
9056 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9057 captured = (ChessSquare) (DEMOTED captured);
9060 p = PieceToNumber((ChessSquare)p);
9061 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9062 board[BOARD_HEIGHT-1-p][1]++;
9063 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9066 } else if (gameInfo.variant == VariantAtomic) {
9067 if (captured != EmptySquare) {
9069 for (y = toY-1; y <= toY+1; y++) {
9070 for (x = toX-1; x <= toX+1; x++) {
9071 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9072 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9073 board[y][x] = EmptySquare;
9077 board[toY][toX] = EmptySquare;
9080 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9081 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9083 if(promoChar == '+') {
9084 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9085 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9086 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9087 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9089 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9090 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9091 // [HGM] superchess: take promotion piece out of holdings
9092 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9093 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9094 if(!--board[k][BOARD_WIDTH-2])
9095 board[k][BOARD_WIDTH-1] = EmptySquare;
9097 if(!--board[BOARD_HEIGHT-1-k][1])
9098 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9104 /* Updates forwardMostMove */
9106 MakeMove(fromX, fromY, toX, toY, promoChar)
9107 int fromX, fromY, toX, toY;
9110 // forwardMostMove++; // [HGM] bare: moved downstream
9112 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9113 int timeLeft; static int lastLoadFlag=0; int king, piece;
9114 piece = boards[forwardMostMove][fromY][fromX];
9115 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9116 if(gameInfo.variant == VariantKnightmate)
9117 king += (int) WhiteUnicorn - (int) WhiteKing;
9118 if(forwardMostMove == 0) {
9120 fprintf(serverMoves, "%s;", second.tidy);
9121 fprintf(serverMoves, "%s;", first.tidy);
9122 if(!blackPlaysFirst)
9123 fprintf(serverMoves, "%s;", second.tidy);
9124 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9125 lastLoadFlag = loadFlag;
9127 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9128 // print castling suffix
9129 if( toY == fromY && piece == king ) {
9131 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9133 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9136 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9137 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9138 boards[forwardMostMove][toY][toX] == EmptySquare
9139 && fromX != toX && fromY != toY)
9140 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9142 if(promoChar != NULLCHAR)
9143 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9145 fprintf(serverMoves, "/%d/%d",
9146 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9147 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9148 else timeLeft = blackTimeRemaining/1000;
9149 fprintf(serverMoves, "/%d", timeLeft);
9151 fflush(serverMoves);
9154 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9155 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9159 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9160 if (commentList[forwardMostMove+1] != NULL) {
9161 free(commentList[forwardMostMove+1]);
9162 commentList[forwardMostMove+1] = NULL;
9164 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9165 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9166 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9167 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9168 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9169 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9170 gameInfo.result = GameUnfinished;
9171 if (gameInfo.resultDetails != NULL) {
9172 free(gameInfo.resultDetails);
9173 gameInfo.resultDetails = NULL;
9175 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9176 moveList[forwardMostMove - 1]);
9177 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9178 PosFlags(forwardMostMove - 1),
9179 fromY, fromX, toY, toX, promoChar,
9180 parseList[forwardMostMove - 1]);
9181 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9187 if(gameInfo.variant != VariantShogi)
9188 strcat(parseList[forwardMostMove - 1], "+");
9192 strcat(parseList[forwardMostMove - 1], "#");
9195 if (appData.debugMode) {
9196 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9201 /* Updates currentMove if not pausing */
9203 ShowMove(fromX, fromY, toX, toY)
9205 int instant = (gameMode == PlayFromGameFile) ?
9206 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9207 if(appData.noGUI) return;
9208 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9210 if (forwardMostMove == currentMove + 1) {
9211 AnimateMove(boards[forwardMostMove - 1],
9212 fromX, fromY, toX, toY);
9214 if (appData.highlightLastMove) {
9215 SetHighlights(fromX, fromY, toX, toY);
9218 currentMove = forwardMostMove;
9221 if (instant) return;
9223 DisplayMove(currentMove - 1);
9224 DrawPosition(FALSE, boards[currentMove]);
9225 DisplayBothClocks();
9226 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9229 void SendEgtPath(ChessProgramState *cps)
9230 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9231 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9233 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9236 char c, *q = name+1, *r, *s;
9238 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9239 while(*p && *p != ',') *q++ = *p++;
9241 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9242 strcmp(name, ",nalimov:") == 0 ) {
9243 // take nalimov path from the menu-changeable option first, if it is defined
9244 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9245 SendToProgram(buf,cps); // send egtbpath command for nalimov
9247 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9248 (s = StrStr(appData.egtFormats, name)) != NULL) {
9249 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9250 s = r = StrStr(s, ":") + 1; // beginning of path info
9251 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9252 c = *r; *r = 0; // temporarily null-terminate path info
9253 *--q = 0; // strip of trailig ':' from name
9254 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9256 SendToProgram(buf,cps); // send egtbpath command for this format
9258 if(*p == ',') p++; // read away comma to position for next format name
9263 InitChessProgram(cps, setup)
9264 ChessProgramState *cps;
9265 int setup; /* [HGM] needed to setup FRC opening position */
9267 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9268 if (appData.noChessProgram) return;
9269 hintRequested = FALSE;
9270 bookRequested = FALSE;
9272 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9273 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9274 if(cps->memSize) { /* [HGM] memory */
9275 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9276 SendToProgram(buf, cps);
9278 SendEgtPath(cps); /* [HGM] EGT */
9279 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9280 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9281 SendToProgram(buf, cps);
9284 SendToProgram(cps->initString, cps);
9285 if (gameInfo.variant != VariantNormal &&
9286 gameInfo.variant != VariantLoadable
9287 /* [HGM] also send variant if board size non-standard */
9288 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9290 char *v = VariantName(gameInfo.variant);
9291 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9292 /* [HGM] in protocol 1 we have to assume all variants valid */
9293 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9294 DisplayFatalError(buf, 0, 1);
9298 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9299 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9300 if( gameInfo.variant == VariantXiangqi )
9301 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9302 if( gameInfo.variant == VariantShogi )
9303 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9304 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9305 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9306 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9307 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9308 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9309 if( gameInfo.variant == VariantCourier )
9310 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9311 if( gameInfo.variant == VariantSuper )
9312 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9313 if( gameInfo.variant == VariantGreat )
9314 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9315 if( gameInfo.variant == VariantSChess )
9316 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9319 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9320 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9321 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9322 if(StrStr(cps->variants, b) == NULL) {
9323 // specific sized variant not known, check if general sizing allowed
9324 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9325 if(StrStr(cps->variants, "boardsize") == NULL) {
9326 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9327 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9328 DisplayFatalError(buf, 0, 1);
9331 /* [HGM] here we really should compare with the maximum supported board size */
9334 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9335 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9336 SendToProgram(buf, cps);
9338 currentlyInitializedVariant = gameInfo.variant;
9340 /* [HGM] send opening position in FRC to first engine */
9342 SendToProgram("force\n", cps);
9344 /* engine is now in force mode! Set flag to wake it up after first move. */
9345 setboardSpoiledMachineBlack = 1;
9349 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9350 SendToProgram(buf, cps);
9352 cps->maybeThinking = FALSE;
9353 cps->offeredDraw = 0;
9354 if (!appData.icsActive) {
9355 SendTimeControl(cps, movesPerSession, timeControl,
9356 timeIncrement, appData.searchDepth,
9359 if (appData.showThinking
9360 // [HGM] thinking: four options require thinking output to be sent
9361 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9363 SendToProgram("post\n", cps);
9365 SendToProgram("hard\n", cps);
9366 if (!appData.ponderNextMove) {
9367 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9368 it without being sure what state we are in first. "hard"
9369 is not a toggle, so that one is OK.
9371 SendToProgram("easy\n", cps);
9374 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9375 SendToProgram(buf, cps);
9377 cps->initDone = TRUE;
9382 StartChessProgram(cps)
9383 ChessProgramState *cps;
9388 if (appData.noChessProgram) return;
9389 cps->initDone = FALSE;
9391 if (strcmp(cps->host, "localhost") == 0) {
9392 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9393 } else if (*appData.remoteShell == NULLCHAR) {
9394 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9396 if (*appData.remoteUser == NULLCHAR) {
9397 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9400 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9401 cps->host, appData.remoteUser, cps->program);
9403 err = StartChildProcess(buf, "", &cps->pr);
9407 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9408 DisplayFatalError(buf, err, 1);
9414 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9415 if (cps->protocolVersion > 1) {
9416 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9417 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9418 cps->comboCnt = 0; // and values of combo boxes
9419 SendToProgram(buf, cps);
9421 SendToProgram("xboard\n", cps);
9426 TwoMachinesEventIfReady P((void))
9428 static int curMess = 0;
9429 if (first.lastPing != first.lastPong) {
9430 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9431 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9434 if (second.lastPing != second.lastPong) {
9435 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9436 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9439 DisplayMessage("", ""); curMess = 0;
9445 CreateTourney(char *name)
9448 if(name[0] == NULLCHAR) return 0;
9449 f = fopen(appData.tourneyFile, "r");
9450 if(f) { // file exists
9451 ParseArgsFromFile(f); // parse it
9453 f = fopen(appData.tourneyFile, "w");
9454 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9455 // create a file with tournament description
9456 fprintf(f, "-participants {%s}\n", appData.participants);
9457 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9458 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9459 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9460 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9461 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9462 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9463 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9464 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9465 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9466 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9467 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9468 fprintf(f, "-results \"\"\n");
9472 appData.noChessProgram = FALSE;
9473 appData.clockMode = TRUE;
9478 #define MAXENGINES 1000
9479 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9481 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9483 char buf[MSG_SIZ], *p, *q;
9487 while(*p && *p != '\n') *q++ = *p++;
9489 if(engineList[i]) free(engineList[i]);
9490 engineList[i] = strdup(buf);
9492 TidyProgramName(engineList[i], "localhost", buf);
9493 if(engineMnemonic[i]) free(engineMnemonic[i]);
9494 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9496 sscanf(q + 8, "%s", buf + strlen(buf));
9499 engineMnemonic[i] = strdup(buf);
9501 if(i > MAXENGINES - 2) break;
9503 engineList[i] = NULL;
9506 // following implemented as macro to avoid type limitations
9507 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9509 void SwapEngines(int n)
9510 { // swap settings for first engine and other engine (so far only some selected options)
9515 SWAP(chessProgram, p)
9517 SWAP(hasOwnBookUCI, h)
9518 SWAP(protocolVersion, h)
9520 SWAP(scoreIsAbsolute, h)
9526 SetPlayer(int player)
9527 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9529 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9530 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9531 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9532 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9533 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9534 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9536 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9537 ParseArgsFromString(resetOptions);
9538 ParseArgsFromString(buf);
9544 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9545 { // determine players from game number
9546 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9548 if(appData.tourneyType == 0) {
9549 roundsPerCycle = (nPlayers - 1) | 1;
9550 pairingsPerRound = nPlayers / 2;
9551 } else if(appData.tourneyType > 0) {
9552 roundsPerCycle = nPlayers - appData.tourneyType;
9553 pairingsPerRound = appData.tourneyType;
9555 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9556 gamesPerCycle = gamesPerRound * roundsPerCycle;
9557 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9558 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9559 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9560 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9561 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9562 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9564 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9565 if(appData.roundSync) *syncInterval = gamesPerRound;
9567 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9569 if(appData.tourneyType == 0) {
9570 if(curPairing == (nPlayers-1)/2 ) {
9571 *whitePlayer = curRound;
9572 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9574 *whitePlayer = curRound - pairingsPerRound + curPairing;
9575 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9576 *blackPlayer = curRound + pairingsPerRound - curPairing;
9577 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9579 } else if(appData.tourneyType > 0) {
9580 *whitePlayer = curPairing;
9581 *blackPlayer = curRound + appData.tourneyType;
9584 // take care of white/black alternation per round.
9585 // For cycles and games this is already taken care of by default, derived from matchGame!
9586 return curRound & 1;
9590 NextTourneyGame(int nr, int *swapColors)
9591 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9593 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9595 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9596 tf = fopen(appData.tourneyFile, "r");
9597 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9598 ParseArgsFromFile(tf); fclose(tf);
9600 p = appData.participants;
9601 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9602 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9605 p = q = appData.results;
9606 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9607 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9608 DisplayMessage(_("Waiting for other game(s)"),"");
9609 waitingForGame = TRUE;
9610 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9613 waitingForGame = FALSE;
9616 if(first.pr != NoProc) return 1; // engines already loaded
9618 // redefine engines, engine dir, etc.
9619 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9620 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9622 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9623 SwapEngines(1); // and make that valid for second engine by swapping
9624 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9625 InitEngine(&second, 1);
9626 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9632 { // performs game initialization that does not invoke engines, and then tries to start the game
9633 int firstWhite, swapColors = 0;
9634 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9635 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9636 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9637 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9638 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9639 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9640 Reset(FALSE, first.pr != NoProc);
9641 appData.noChessProgram = FALSE;
9642 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9646 void UserAdjudicationEvent( int result )
9648 ChessMove gameResult = GameIsDrawn;
9651 gameResult = WhiteWins;
9653 else if( result < 0 ) {
9654 gameResult = BlackWins;
9657 if( gameMode == TwoMachinesPlay ) {
9658 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9663 // [HGM] save: calculate checksum of game to make games easily identifiable
9664 int StringCheckSum(char *s)
9667 if(s==NULL) return 0;
9668 while(*s) i = i*259 + *s++;
9675 for(i=backwardMostMove; i<forwardMostMove; i++) {
9676 sum += pvInfoList[i].depth;
9677 sum += StringCheckSum(parseList[i]);
9678 sum += StringCheckSum(commentList[i]);
9681 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9682 return sum + StringCheckSum(commentList[i]);
9683 } // end of save patch
9686 GameEnds(result, resultDetails, whosays)
9688 char *resultDetails;
9691 GameMode nextGameMode;
9693 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9695 if(endingGame) return; /* [HGM] crash: forbid recursion */
9697 if(twoBoards) { // [HGM] dual: switch back to one board
9698 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9699 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9701 if (appData.debugMode) {
9702 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9703 result, resultDetails ? resultDetails : "(null)", whosays);
9706 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9708 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9709 /* If we are playing on ICS, the server decides when the
9710 game is over, but the engine can offer to draw, claim
9714 if (appData.zippyPlay && first.initDone) {
9715 if (result == GameIsDrawn) {
9716 /* In case draw still needs to be claimed */
9717 SendToICS(ics_prefix);
9718 SendToICS("draw\n");
9719 } else if (StrCaseStr(resultDetails, "resign")) {
9720 SendToICS(ics_prefix);
9721 SendToICS("resign\n");
9725 endingGame = 0; /* [HGM] crash */
9729 /* If we're loading the game from a file, stop */
9730 if (whosays == GE_FILE) {
9731 (void) StopLoadGameTimer();
9735 /* Cancel draw offers */
9736 first.offeredDraw = second.offeredDraw = 0;
9738 /* If this is an ICS game, only ICS can really say it's done;
9739 if not, anyone can. */
9740 isIcsGame = (gameMode == IcsPlayingWhite ||
9741 gameMode == IcsPlayingBlack ||
9742 gameMode == IcsObserving ||
9743 gameMode == IcsExamining);
9745 if (!isIcsGame || whosays == GE_ICS) {
9746 /* OK -- not an ICS game, or ICS said it was done */
9748 if (!isIcsGame && !appData.noChessProgram)
9749 SetUserThinkingEnables();
9751 /* [HGM] if a machine claims the game end we verify this claim */
9752 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9753 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9755 ChessMove trueResult = (ChessMove) -1;
9757 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9758 first.twoMachinesColor[0] :
9759 second.twoMachinesColor[0] ;
9761 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9762 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9763 /* [HGM] verify: engine mate claims accepted if they were flagged */
9764 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9766 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9767 /* [HGM] verify: engine mate claims accepted if they were flagged */
9768 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9770 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9771 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9774 // now verify win claims, but not in drop games, as we don't understand those yet
9775 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9776 || gameInfo.variant == VariantGreat) &&
9777 (result == WhiteWins && claimer == 'w' ||
9778 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9779 if (appData.debugMode) {
9780 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9781 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9783 if(result != trueResult) {
9784 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9785 result = claimer == 'w' ? BlackWins : WhiteWins;
9786 resultDetails = buf;
9789 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9790 && (forwardMostMove <= backwardMostMove ||
9791 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9792 (claimer=='b')==(forwardMostMove&1))
9794 /* [HGM] verify: draws that were not flagged are false claims */
9795 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9796 result = claimer == 'w' ? BlackWins : WhiteWins;
9797 resultDetails = buf;
9799 /* (Claiming a loss is accepted no questions asked!) */
9801 /* [HGM] bare: don't allow bare King to win */
9802 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9803 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9804 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9805 && result != GameIsDrawn)
9806 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9807 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9808 int p = (signed char)boards[forwardMostMove][i][j] - color;
9809 if(p >= 0 && p <= (int)WhiteKing) k++;
9811 if (appData.debugMode) {
9812 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9813 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9816 result = GameIsDrawn;
9817 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9818 resultDetails = buf;
9824 if(serverMoves != NULL && !loadFlag) { char c = '=';
9825 if(result==WhiteWins) c = '+';
9826 if(result==BlackWins) c = '-';
9827 if(resultDetails != NULL)
9828 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9830 if (resultDetails != NULL) {
9831 gameInfo.result = result;
9832 gameInfo.resultDetails = StrSave(resultDetails);
9834 /* display last move only if game was not loaded from file */
9835 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9836 DisplayMove(currentMove - 1);
9838 if (forwardMostMove != 0) {
9839 if (gameMode != PlayFromGameFile && gameMode != EditGame
9840 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9842 if (*appData.saveGameFile != NULLCHAR) {
9843 SaveGameToFile(appData.saveGameFile, TRUE);
9844 } else if (appData.autoSaveGames) {
9847 if (*appData.savePositionFile != NULLCHAR) {
9848 SavePositionToFile(appData.savePositionFile);
9853 /* Tell program how game ended in case it is learning */
9854 /* [HGM] Moved this to after saving the PGN, just in case */
9855 /* engine died and we got here through time loss. In that */
9856 /* case we will get a fatal error writing the pipe, which */
9857 /* would otherwise lose us the PGN. */
9858 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9859 /* output during GameEnds should never be fatal anymore */
9860 if (gameMode == MachinePlaysWhite ||
9861 gameMode == MachinePlaysBlack ||
9862 gameMode == TwoMachinesPlay ||
9863 gameMode == IcsPlayingWhite ||
9864 gameMode == IcsPlayingBlack ||
9865 gameMode == BeginningOfGame) {
9867 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9869 if (first.pr != NoProc) {
9870 SendToProgram(buf, &first);
9872 if (second.pr != NoProc &&
9873 gameMode == TwoMachinesPlay) {
9874 SendToProgram(buf, &second);
9879 if (appData.icsActive) {
9880 if (appData.quietPlay &&
9881 (gameMode == IcsPlayingWhite ||
9882 gameMode == IcsPlayingBlack)) {
9883 SendToICS(ics_prefix);
9884 SendToICS("set shout 1\n");
9886 nextGameMode = IcsIdle;
9887 ics_user_moved = FALSE;
9888 /* clean up premove. It's ugly when the game has ended and the
9889 * premove highlights are still on the board.
9893 ClearPremoveHighlights();
9894 DrawPosition(FALSE, boards[currentMove]);
9896 if (whosays == GE_ICS) {
9899 if (gameMode == IcsPlayingWhite)
9901 else if(gameMode == IcsPlayingBlack)
9905 if (gameMode == IcsPlayingBlack)
9907 else if(gameMode == IcsPlayingWhite)
9914 PlayIcsUnfinishedSound();
9917 } else if (gameMode == EditGame ||
9918 gameMode == PlayFromGameFile ||
9919 gameMode == AnalyzeMode ||
9920 gameMode == AnalyzeFile) {
9921 nextGameMode = gameMode;
9923 nextGameMode = EndOfGame;
9928 nextGameMode = gameMode;
9931 if (appData.noChessProgram) {
9932 gameMode = nextGameMode;
9934 endingGame = 0; /* [HGM] crash */
9939 /* Put first chess program into idle state */
9940 if (first.pr != NoProc &&
9941 (gameMode == MachinePlaysWhite ||
9942 gameMode == MachinePlaysBlack ||
9943 gameMode == TwoMachinesPlay ||
9944 gameMode == IcsPlayingWhite ||
9945 gameMode == IcsPlayingBlack ||
9946 gameMode == BeginningOfGame)) {
9947 SendToProgram("force\n", &first);
9948 if (first.usePing) {
9950 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9951 SendToProgram(buf, &first);
9954 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9955 /* Kill off first chess program */
9956 if (first.isr != NULL)
9957 RemoveInputSource(first.isr);
9960 if (first.pr != NoProc) {
9962 DoSleep( appData.delayBeforeQuit );
9963 SendToProgram("quit\n", &first);
9964 DoSleep( appData.delayAfterQuit );
9965 DestroyChildProcess(first.pr, first.useSigterm);
9970 /* Put second chess program into idle state */
9971 if (second.pr != NoProc &&
9972 gameMode == TwoMachinesPlay) {
9973 SendToProgram("force\n", &second);
9974 if (second.usePing) {
9976 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9977 SendToProgram(buf, &second);
9980 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9981 /* Kill off second chess program */
9982 if (second.isr != NULL)
9983 RemoveInputSource(second.isr);
9986 if (second.pr != NoProc) {
9987 DoSleep( appData.delayBeforeQuit );
9988 SendToProgram("quit\n", &second);
9989 DoSleep( appData.delayAfterQuit );
9990 DestroyChildProcess(second.pr, second.useSigterm);
9995 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10000 if (first.twoMachinesColor[0] == 'w') {
10003 second.matchWins++;
10008 if (first.twoMachinesColor[0] == 'b') {
10011 second.matchWins++;
10014 case GameUnfinished:
10020 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10021 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10022 ReserveGame(nextGame, resChar); // sets nextGame
10023 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10024 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10026 if (nextGame <= appData.matchGames) {
10027 gameMode = nextGameMode;
10028 matchGame = nextGame; // this will be overruled in tourney mode!
10029 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10030 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10031 endingGame = 0; /* [HGM] crash */
10034 gameMode = nextGameMode;
10035 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10036 first.tidy, second.tidy,
10037 first.matchWins, second.matchWins,
10038 appData.matchGames - (first.matchWins + second.matchWins));
10039 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10040 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10041 first.twoMachinesColor = "black\n";
10042 second.twoMachinesColor = "white\n";
10044 first.twoMachinesColor = "white\n";
10045 second.twoMachinesColor = "black\n";
10049 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10050 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10052 gameMode = nextGameMode;
10054 endingGame = 0; /* [HGM] crash */
10055 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10056 if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10057 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10058 DisplayNote(ranking ? ranking : buf);
10060 if(ranking) free(ranking);
10064 /* Assumes program was just initialized (initString sent).
10065 Leaves program in force mode. */
10067 FeedMovesToProgram(cps, upto)
10068 ChessProgramState *cps;
10073 if (appData.debugMode)
10074 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10075 startedFromSetupPosition ? "position and " : "",
10076 backwardMostMove, upto, cps->which);
10077 if(currentlyInitializedVariant != gameInfo.variant) {
10079 // [HGM] variantswitch: make engine aware of new variant
10080 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10081 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10082 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10083 SendToProgram(buf, cps);
10084 currentlyInitializedVariant = gameInfo.variant;
10086 SendToProgram("force\n", cps);
10087 if (startedFromSetupPosition) {
10088 SendBoard(cps, backwardMostMove);
10089 if (appData.debugMode) {
10090 fprintf(debugFP, "feedMoves\n");
10093 for (i = backwardMostMove; i < upto; i++) {
10094 SendMoveToProgram(i, cps);
10100 ResurrectChessProgram()
10102 /* The chess program may have exited.
10103 If so, restart it and feed it all the moves made so far. */
10104 static int doInit = 0;
10106 if (appData.noChessProgram) return 1;
10108 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10109 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10110 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10111 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10113 if (first.pr != NoProc) return 1;
10114 StartChessProgram(&first);
10116 InitChessProgram(&first, FALSE);
10117 FeedMovesToProgram(&first, currentMove);
10119 if (!first.sendTime) {
10120 /* can't tell gnuchess what its clock should read,
10121 so we bow to its notion. */
10123 timeRemaining[0][currentMove] = whiteTimeRemaining;
10124 timeRemaining[1][currentMove] = blackTimeRemaining;
10127 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10128 appData.icsEngineAnalyze) && first.analysisSupport) {
10129 SendToProgram("analyze\n", &first);
10130 first.analyzing = TRUE;
10136 * Button procedures
10139 Reset(redraw, init)
10144 if (appData.debugMode) {
10145 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10146 redraw, init, gameMode);
10148 CleanupTail(); // [HGM] vari: delete any stored variations
10149 pausing = pauseExamInvalid = FALSE;
10150 startedFromSetupPosition = blackPlaysFirst = FALSE;
10152 whiteFlag = blackFlag = FALSE;
10153 userOfferedDraw = FALSE;
10154 hintRequested = bookRequested = FALSE;
10155 first.maybeThinking = FALSE;
10156 second.maybeThinking = FALSE;
10157 first.bookSuspend = FALSE; // [HGM] book
10158 second.bookSuspend = FALSE;
10159 thinkOutput[0] = NULLCHAR;
10160 lastHint[0] = NULLCHAR;
10161 ClearGameInfo(&gameInfo);
10162 gameInfo.variant = StringToVariant(appData.variant);
10163 ics_user_moved = ics_clock_paused = FALSE;
10164 ics_getting_history = H_FALSE;
10166 white_holding[0] = black_holding[0] = NULLCHAR;
10167 ClearProgramStats();
10168 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10172 flipView = appData.flipView;
10173 ClearPremoveHighlights();
10174 gotPremove = FALSE;
10175 alarmSounded = FALSE;
10177 GameEnds(EndOfFile, NULL, GE_PLAYER);
10178 if(appData.serverMovesName != NULL) {
10179 /* [HGM] prepare to make moves file for broadcasting */
10180 clock_t t = clock();
10181 if(serverMoves != NULL) fclose(serverMoves);
10182 serverMoves = fopen(appData.serverMovesName, "r");
10183 if(serverMoves != NULL) {
10184 fclose(serverMoves);
10185 /* delay 15 sec before overwriting, so all clients can see end */
10186 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10188 serverMoves = fopen(appData.serverMovesName, "w");
10192 gameMode = BeginningOfGame;
10194 if(appData.icsActive) gameInfo.variant = VariantNormal;
10195 currentMove = forwardMostMove = backwardMostMove = 0;
10196 InitPosition(redraw);
10197 for (i = 0; i < MAX_MOVES; i++) {
10198 if (commentList[i] != NULL) {
10199 free(commentList[i]);
10200 commentList[i] = NULL;
10204 timeRemaining[0][0] = whiteTimeRemaining;
10205 timeRemaining[1][0] = blackTimeRemaining;
10207 if (first.pr == NULL) {
10208 StartChessProgram(&first);
10211 InitChessProgram(&first, startedFromSetupPosition);
10214 DisplayMessage("", "");
10215 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10216 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10223 if (!AutoPlayOneMove())
10225 if (matchMode || appData.timeDelay == 0)
10227 if (appData.timeDelay < 0)
10229 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10238 int fromX, fromY, toX, toY;
10240 if (appData.debugMode) {
10241 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10244 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10247 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10248 pvInfoList[currentMove].depth = programStats.depth;
10249 pvInfoList[currentMove].score = programStats.score;
10250 pvInfoList[currentMove].time = 0;
10251 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10254 if (currentMove >= forwardMostMove) {
10255 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10256 gameMode = EditGame;
10259 /* [AS] Clear current move marker at the end of a game */
10260 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10265 toX = moveList[currentMove][2] - AAA;
10266 toY = moveList[currentMove][3] - ONE;
10268 if (moveList[currentMove][1] == '@') {
10269 if (appData.highlightLastMove) {
10270 SetHighlights(-1, -1, toX, toY);
10273 fromX = moveList[currentMove][0] - AAA;
10274 fromY = moveList[currentMove][1] - ONE;
10276 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10278 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10280 if (appData.highlightLastMove) {
10281 SetHighlights(fromX, fromY, toX, toY);
10284 DisplayMove(currentMove);
10285 SendMoveToProgram(currentMove++, &first);
10286 DisplayBothClocks();
10287 DrawPosition(FALSE, boards[currentMove]);
10288 // [HGM] PV info: always display, routine tests if empty
10289 DisplayComment(currentMove - 1, commentList[currentMove]);
10295 LoadGameOneMove(readAhead)
10296 ChessMove readAhead;
10298 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10299 char promoChar = NULLCHAR;
10300 ChessMove moveType;
10301 char move[MSG_SIZ];
10304 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10305 gameMode != AnalyzeMode && gameMode != Training) {
10310 yyboardindex = forwardMostMove;
10311 if (readAhead != EndOfFile) {
10312 moveType = readAhead;
10314 if (gameFileFP == NULL)
10316 moveType = (ChessMove) Myylex();
10320 switch (moveType) {
10322 if (appData.debugMode)
10323 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10326 /* append the comment but don't display it */
10327 AppendComment(currentMove, p, FALSE);
10330 case WhiteCapturesEnPassant:
10331 case BlackCapturesEnPassant:
10332 case WhitePromotion:
10333 case BlackPromotion:
10334 case WhiteNonPromotion:
10335 case BlackNonPromotion:
10337 case WhiteKingSideCastle:
10338 case WhiteQueenSideCastle:
10339 case BlackKingSideCastle:
10340 case BlackQueenSideCastle:
10341 case WhiteKingSideCastleWild:
10342 case WhiteQueenSideCastleWild:
10343 case BlackKingSideCastleWild:
10344 case BlackQueenSideCastleWild:
10346 case WhiteHSideCastleFR:
10347 case WhiteASideCastleFR:
10348 case BlackHSideCastleFR:
10349 case BlackASideCastleFR:
10351 if (appData.debugMode)
10352 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10353 fromX = currentMoveString[0] - AAA;
10354 fromY = currentMoveString[1] - ONE;
10355 toX = currentMoveString[2] - AAA;
10356 toY = currentMoveString[3] - ONE;
10357 promoChar = currentMoveString[4];
10362 if (appData.debugMode)
10363 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10364 fromX = moveType == WhiteDrop ?
10365 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10366 (int) CharToPiece(ToLower(currentMoveString[0]));
10368 toX = currentMoveString[2] - AAA;
10369 toY = currentMoveString[3] - ONE;
10375 case GameUnfinished:
10376 if (appData.debugMode)
10377 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10378 p = strchr(yy_text, '{');
10379 if (p == NULL) p = strchr(yy_text, '(');
10382 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10384 q = strchr(p, *p == '{' ? '}' : ')');
10385 if (q != NULL) *q = NULLCHAR;
10388 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10389 GameEnds(moveType, p, GE_FILE);
10391 if (cmailMsgLoaded) {
10393 flipView = WhiteOnMove(currentMove);
10394 if (moveType == GameUnfinished) flipView = !flipView;
10395 if (appData.debugMode)
10396 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10401 if (appData.debugMode)
10402 fprintf(debugFP, "Parser hit end of file\n");
10403 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10409 if (WhiteOnMove(currentMove)) {
10410 GameEnds(BlackWins, "Black mates", GE_FILE);
10412 GameEnds(WhiteWins, "White mates", GE_FILE);
10416 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10422 case MoveNumberOne:
10423 if (lastLoadGameStart == GNUChessGame) {
10424 /* GNUChessGames have numbers, but they aren't move numbers */
10425 if (appData.debugMode)
10426 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10427 yy_text, (int) moveType);
10428 return LoadGameOneMove(EndOfFile); /* tail recursion */
10430 /* else fall thru */
10435 /* Reached start of next game in file */
10436 if (appData.debugMode)
10437 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10438 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10444 if (WhiteOnMove(currentMove)) {
10445 GameEnds(BlackWins, "Black mates", GE_FILE);
10447 GameEnds(WhiteWins, "White mates", GE_FILE);
10451 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10457 case PositionDiagram: /* should not happen; ignore */
10458 case ElapsedTime: /* ignore */
10459 case NAG: /* ignore */
10460 if (appData.debugMode)
10461 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10462 yy_text, (int) moveType);
10463 return LoadGameOneMove(EndOfFile); /* tail recursion */
10466 if (appData.testLegality) {
10467 if (appData.debugMode)
10468 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10469 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10470 (forwardMostMove / 2) + 1,
10471 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10472 DisplayError(move, 0);
10475 if (appData.debugMode)
10476 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10477 yy_text, currentMoveString);
10478 fromX = currentMoveString[0] - AAA;
10479 fromY = currentMoveString[1] - ONE;
10480 toX = currentMoveString[2] - AAA;
10481 toY = currentMoveString[3] - ONE;
10482 promoChar = currentMoveString[4];
10486 case AmbiguousMove:
10487 if (appData.debugMode)
10488 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10489 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10490 (forwardMostMove / 2) + 1,
10491 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10492 DisplayError(move, 0);
10497 case ImpossibleMove:
10498 if (appData.debugMode)
10499 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10500 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10501 (forwardMostMove / 2) + 1,
10502 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10503 DisplayError(move, 0);
10509 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10510 DrawPosition(FALSE, boards[currentMove]);
10511 DisplayBothClocks();
10512 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10513 DisplayComment(currentMove - 1, commentList[currentMove]);
10515 (void) StopLoadGameTimer();
10517 cmailOldMove = forwardMostMove;
10520 /* currentMoveString is set as a side-effect of yylex */
10522 thinkOutput[0] = NULLCHAR;
10523 MakeMove(fromX, fromY, toX, toY, promoChar);
10524 currentMove = forwardMostMove;
10529 /* Load the nth game from the given file */
10531 LoadGameFromFile(filename, n, title, useList)
10535 /*Boolean*/ int useList;
10540 if (strcmp(filename, "-") == 0) {
10544 f = fopen(filename, "rb");
10546 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10547 DisplayError(buf, errno);
10551 if (fseek(f, 0, 0) == -1) {
10552 /* f is not seekable; probably a pipe */
10555 if (useList && n == 0) {
10556 int error = GameListBuild(f);
10558 DisplayError(_("Cannot build game list"), error);
10559 } else if (!ListEmpty(&gameList) &&
10560 ((ListGame *) gameList.tailPred)->number > 1) {
10561 GameListPopUp(f, title);
10568 return LoadGame(f, n, title, FALSE);
10573 MakeRegisteredMove()
10575 int fromX, fromY, toX, toY;
10577 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10578 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10581 if (appData.debugMode)
10582 fprintf(debugFP, "Restoring %s for game %d\n",
10583 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10585 thinkOutput[0] = NULLCHAR;
10586 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10587 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10588 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10589 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10590 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10591 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10592 MakeMove(fromX, fromY, toX, toY, promoChar);
10593 ShowMove(fromX, fromY, toX, toY);
10595 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10602 if (WhiteOnMove(currentMove)) {
10603 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10605 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10610 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10617 if (WhiteOnMove(currentMove)) {
10618 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10620 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10625 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10636 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10638 CmailLoadGame(f, gameNumber, title, useList)
10646 if (gameNumber > nCmailGames) {
10647 DisplayError(_("No more games in this message"), 0);
10650 if (f == lastLoadGameFP) {
10651 int offset = gameNumber - lastLoadGameNumber;
10653 cmailMsg[0] = NULLCHAR;
10654 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10655 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10656 nCmailMovesRegistered--;
10658 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10659 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10660 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10663 if (! RegisterMove()) return FALSE;
10667 retVal = LoadGame(f, gameNumber, title, useList);
10669 /* Make move registered during previous look at this game, if any */
10670 MakeRegisteredMove();
10672 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10673 commentList[currentMove]
10674 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10675 DisplayComment(currentMove - 1, commentList[currentMove]);
10681 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10686 int gameNumber = lastLoadGameNumber + offset;
10687 if (lastLoadGameFP == NULL) {
10688 DisplayError(_("No game has been loaded yet"), 0);
10691 if (gameNumber <= 0) {
10692 DisplayError(_("Can't back up any further"), 0);
10695 if (cmailMsgLoaded) {
10696 return CmailLoadGame(lastLoadGameFP, gameNumber,
10697 lastLoadGameTitle, lastLoadGameUseList);
10699 return LoadGame(lastLoadGameFP, gameNumber,
10700 lastLoadGameTitle, lastLoadGameUseList);
10706 /* Load the nth game from open file f */
10708 LoadGame(f, gameNumber, title, useList)
10716 int gn = gameNumber;
10717 ListGame *lg = NULL;
10718 int numPGNTags = 0;
10720 GameMode oldGameMode;
10721 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10723 if (appData.debugMode)
10724 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10726 if (gameMode == Training )
10727 SetTrainingModeOff();
10729 oldGameMode = gameMode;
10730 if (gameMode != BeginningOfGame) {
10731 Reset(FALSE, TRUE);
10735 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10736 fclose(lastLoadGameFP);
10740 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10743 fseek(f, lg->offset, 0);
10744 GameListHighlight(gameNumber);
10748 DisplayError(_("Game number out of range"), 0);
10753 if (fseek(f, 0, 0) == -1) {
10754 if (f == lastLoadGameFP ?
10755 gameNumber == lastLoadGameNumber + 1 :
10759 DisplayError(_("Can't seek on game file"), 0);
10764 lastLoadGameFP = f;
10765 lastLoadGameNumber = gameNumber;
10766 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10767 lastLoadGameUseList = useList;
10771 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10772 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10773 lg->gameInfo.black);
10775 } else if (*title != NULLCHAR) {
10776 if (gameNumber > 1) {
10777 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10780 DisplayTitle(title);
10784 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10785 gameMode = PlayFromGameFile;
10789 currentMove = forwardMostMove = backwardMostMove = 0;
10790 CopyBoard(boards[0], initialPosition);
10794 * Skip the first gn-1 games in the file.
10795 * Also skip over anything that precedes an identifiable
10796 * start of game marker, to avoid being confused by
10797 * garbage at the start of the file. Currently
10798 * recognized start of game markers are the move number "1",
10799 * the pattern "gnuchess .* game", the pattern
10800 * "^[#;%] [^ ]* game file", and a PGN tag block.
10801 * A game that starts with one of the latter two patterns
10802 * will also have a move number 1, possibly
10803 * following a position diagram.
10804 * 5-4-02: Let's try being more lenient and allowing a game to
10805 * start with an unnumbered move. Does that break anything?
10807 cm = lastLoadGameStart = EndOfFile;
10809 yyboardindex = forwardMostMove;
10810 cm = (ChessMove) Myylex();
10813 if (cmailMsgLoaded) {
10814 nCmailGames = CMAIL_MAX_GAMES - gn;
10817 DisplayError(_("Game not found in file"), 0);
10824 lastLoadGameStart = cm;
10827 case MoveNumberOne:
10828 switch (lastLoadGameStart) {
10833 case MoveNumberOne:
10835 gn--; /* count this game */
10836 lastLoadGameStart = cm;
10845 switch (lastLoadGameStart) {
10848 case MoveNumberOne:
10850 gn--; /* count this game */
10851 lastLoadGameStart = cm;
10854 lastLoadGameStart = cm; /* game counted already */
10862 yyboardindex = forwardMostMove;
10863 cm = (ChessMove) Myylex();
10864 } while (cm == PGNTag || cm == Comment);
10871 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10872 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10873 != CMAIL_OLD_RESULT) {
10875 cmailResult[ CMAIL_MAX_GAMES
10876 - gn - 1] = CMAIL_OLD_RESULT;
10882 /* Only a NormalMove can be at the start of a game
10883 * without a position diagram. */
10884 if (lastLoadGameStart == EndOfFile ) {
10886 lastLoadGameStart = MoveNumberOne;
10895 if (appData.debugMode)
10896 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10898 if (cm == XBoardGame) {
10899 /* Skip any header junk before position diagram and/or move 1 */
10901 yyboardindex = forwardMostMove;
10902 cm = (ChessMove) Myylex();
10904 if (cm == EndOfFile ||
10905 cm == GNUChessGame || cm == XBoardGame) {
10906 /* Empty game; pretend end-of-file and handle later */
10911 if (cm == MoveNumberOne || cm == PositionDiagram ||
10912 cm == PGNTag || cm == Comment)
10915 } else if (cm == GNUChessGame) {
10916 if (gameInfo.event != NULL) {
10917 free(gameInfo.event);
10919 gameInfo.event = StrSave(yy_text);
10922 startedFromSetupPosition = FALSE;
10923 while (cm == PGNTag) {
10924 if (appData.debugMode)
10925 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10926 err = ParsePGNTag(yy_text, &gameInfo);
10927 if (!err) numPGNTags++;
10929 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10930 if(gameInfo.variant != oldVariant) {
10931 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10932 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10933 InitPosition(TRUE);
10934 oldVariant = gameInfo.variant;
10935 if (appData.debugMode)
10936 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10940 if (gameInfo.fen != NULL) {
10941 Board initial_position;
10942 startedFromSetupPosition = TRUE;
10943 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10945 DisplayError(_("Bad FEN position in file"), 0);
10948 CopyBoard(boards[0], initial_position);
10949 if (blackPlaysFirst) {
10950 currentMove = forwardMostMove = backwardMostMove = 1;
10951 CopyBoard(boards[1], initial_position);
10952 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10953 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10954 timeRemaining[0][1] = whiteTimeRemaining;
10955 timeRemaining[1][1] = blackTimeRemaining;
10956 if (commentList[0] != NULL) {
10957 commentList[1] = commentList[0];
10958 commentList[0] = NULL;
10961 currentMove = forwardMostMove = backwardMostMove = 0;
10963 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10965 initialRulePlies = FENrulePlies;
10966 for( i=0; i< nrCastlingRights; i++ )
10967 initialRights[i] = initial_position[CASTLING][i];
10969 yyboardindex = forwardMostMove;
10970 free(gameInfo.fen);
10971 gameInfo.fen = NULL;
10974 yyboardindex = forwardMostMove;
10975 cm = (ChessMove) Myylex();
10977 /* Handle comments interspersed among the tags */
10978 while (cm == Comment) {
10980 if (appData.debugMode)
10981 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10983 AppendComment(currentMove, p, FALSE);
10984 yyboardindex = forwardMostMove;
10985 cm = (ChessMove) Myylex();
10989 /* don't rely on existence of Event tag since if game was
10990 * pasted from clipboard the Event tag may not exist
10992 if (numPGNTags > 0){
10994 if (gameInfo.variant == VariantNormal) {
10995 VariantClass v = StringToVariant(gameInfo.event);
10996 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10997 if(v < VariantShogi) gameInfo.variant = v;
11000 if( appData.autoDisplayTags ) {
11001 tags = PGNTags(&gameInfo);
11002 TagsPopUp(tags, CmailMsg());
11007 /* Make something up, but don't display it now */
11012 if (cm == PositionDiagram) {
11015 Board initial_position;
11017 if (appData.debugMode)
11018 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11020 if (!startedFromSetupPosition) {
11022 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11023 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11034 initial_position[i][j++] = CharToPiece(*p);
11037 while (*p == ' ' || *p == '\t' ||
11038 *p == '\n' || *p == '\r') p++;
11040 if (strncmp(p, "black", strlen("black"))==0)
11041 blackPlaysFirst = TRUE;
11043 blackPlaysFirst = FALSE;
11044 startedFromSetupPosition = TRUE;
11046 CopyBoard(boards[0], initial_position);
11047 if (blackPlaysFirst) {
11048 currentMove = forwardMostMove = backwardMostMove = 1;
11049 CopyBoard(boards[1], initial_position);
11050 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11051 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11052 timeRemaining[0][1] = whiteTimeRemaining;
11053 timeRemaining[1][1] = blackTimeRemaining;
11054 if (commentList[0] != NULL) {
11055 commentList[1] = commentList[0];
11056 commentList[0] = NULL;
11059 currentMove = forwardMostMove = backwardMostMove = 0;
11062 yyboardindex = forwardMostMove;
11063 cm = (ChessMove) Myylex();
11066 if (first.pr == NoProc) {
11067 StartChessProgram(&first);
11069 InitChessProgram(&first, FALSE);
11070 SendToProgram("force\n", &first);
11071 if (startedFromSetupPosition) {
11072 SendBoard(&first, forwardMostMove);
11073 if (appData.debugMode) {
11074 fprintf(debugFP, "Load Game\n");
11076 DisplayBothClocks();
11079 /* [HGM] server: flag to write setup moves in broadcast file as one */
11080 loadFlag = appData.suppressLoadMoves;
11082 while (cm == Comment) {
11084 if (appData.debugMode)
11085 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11087 AppendComment(currentMove, p, FALSE);
11088 yyboardindex = forwardMostMove;
11089 cm = (ChessMove) Myylex();
11092 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11093 cm == WhiteWins || cm == BlackWins ||
11094 cm == GameIsDrawn || cm == GameUnfinished) {
11095 DisplayMessage("", _("No moves in game"));
11096 if (cmailMsgLoaded) {
11097 if (appData.debugMode)
11098 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11102 DrawPosition(FALSE, boards[currentMove]);
11103 DisplayBothClocks();
11104 gameMode = EditGame;
11111 // [HGM] PV info: routine tests if comment empty
11112 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11113 DisplayComment(currentMove - 1, commentList[currentMove]);
11115 if (!matchMode && appData.timeDelay != 0)
11116 DrawPosition(FALSE, boards[currentMove]);
11118 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11119 programStats.ok_to_send = 1;
11122 /* if the first token after the PGN tags is a move
11123 * and not move number 1, retrieve it from the parser
11125 if (cm != MoveNumberOne)
11126 LoadGameOneMove(cm);
11128 /* load the remaining moves from the file */
11129 while (LoadGameOneMove(EndOfFile)) {
11130 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11131 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11134 /* rewind to the start of the game */
11135 currentMove = backwardMostMove;
11137 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11139 if (oldGameMode == AnalyzeFile ||
11140 oldGameMode == AnalyzeMode) {
11141 AnalyzeFileEvent();
11144 if (matchMode || appData.timeDelay == 0) {
11146 gameMode = EditGame;
11148 } else if (appData.timeDelay > 0) {
11149 AutoPlayGameLoop();
11152 if (appData.debugMode)
11153 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11155 loadFlag = 0; /* [HGM] true game starts */
11159 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11161 ReloadPosition(offset)
11164 int positionNumber = lastLoadPositionNumber + offset;
11165 if (lastLoadPositionFP == NULL) {
11166 DisplayError(_("No position has been loaded yet"), 0);
11169 if (positionNumber <= 0) {
11170 DisplayError(_("Can't back up any further"), 0);
11173 return LoadPosition(lastLoadPositionFP, positionNumber,
11174 lastLoadPositionTitle);
11177 /* Load the nth position from the given file */
11179 LoadPositionFromFile(filename, n, title)
11187 if (strcmp(filename, "-") == 0) {
11188 return LoadPosition(stdin, n, "stdin");
11190 f = fopen(filename, "rb");
11192 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11193 DisplayError(buf, errno);
11196 return LoadPosition(f, n, title);
11201 /* Load the nth position from the given open file, and close it */
11203 LoadPosition(f, positionNumber, title)
11205 int positionNumber;
11208 char *p, line[MSG_SIZ];
11209 Board initial_position;
11210 int i, j, fenMode, pn;
11212 if (gameMode == Training )
11213 SetTrainingModeOff();
11215 if (gameMode != BeginningOfGame) {
11216 Reset(FALSE, TRUE);
11218 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11219 fclose(lastLoadPositionFP);
11221 if (positionNumber == 0) positionNumber = 1;
11222 lastLoadPositionFP = f;
11223 lastLoadPositionNumber = positionNumber;
11224 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11225 if (first.pr == NoProc) {
11226 StartChessProgram(&first);
11227 InitChessProgram(&first, FALSE);
11229 pn = positionNumber;
11230 if (positionNumber < 0) {
11231 /* Negative position number means to seek to that byte offset */
11232 if (fseek(f, -positionNumber, 0) == -1) {
11233 DisplayError(_("Can't seek on position file"), 0);
11238 if (fseek(f, 0, 0) == -1) {
11239 if (f == lastLoadPositionFP ?
11240 positionNumber == lastLoadPositionNumber + 1 :
11241 positionNumber == 1) {
11244 DisplayError(_("Can't seek on position file"), 0);
11249 /* See if this file is FEN or old-style xboard */
11250 if (fgets(line, MSG_SIZ, f) == NULL) {
11251 DisplayError(_("Position not found in file"), 0);
11254 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11255 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11258 if (fenMode || line[0] == '#') pn--;
11260 /* skip positions before number pn */
11261 if (fgets(line, MSG_SIZ, f) == NULL) {
11263 DisplayError(_("Position not found in file"), 0);
11266 if (fenMode || line[0] == '#') pn--;
11271 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11272 DisplayError(_("Bad FEN position in file"), 0);
11276 (void) fgets(line, MSG_SIZ, f);
11277 (void) fgets(line, MSG_SIZ, f);
11279 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11280 (void) fgets(line, MSG_SIZ, f);
11281 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11284 initial_position[i][j++] = CharToPiece(*p);
11288 blackPlaysFirst = FALSE;
11290 (void) fgets(line, MSG_SIZ, f);
11291 if (strncmp(line, "black", strlen("black"))==0)
11292 blackPlaysFirst = TRUE;
11295 startedFromSetupPosition = TRUE;
11297 SendToProgram("force\n", &first);
11298 CopyBoard(boards[0], initial_position);
11299 if (blackPlaysFirst) {
11300 currentMove = forwardMostMove = backwardMostMove = 1;
11301 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11302 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11303 CopyBoard(boards[1], initial_position);
11304 DisplayMessage("", _("Black to play"));
11306 currentMove = forwardMostMove = backwardMostMove = 0;
11307 DisplayMessage("", _("White to play"));
11309 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11310 SendBoard(&first, forwardMostMove);
11311 if (appData.debugMode) {
11313 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11314 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11315 fprintf(debugFP, "Load Position\n");
11318 if (positionNumber > 1) {
11319 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11320 DisplayTitle(line);
11322 DisplayTitle(title);
11324 gameMode = EditGame;
11327 timeRemaining[0][1] = whiteTimeRemaining;
11328 timeRemaining[1][1] = blackTimeRemaining;
11329 DrawPosition(FALSE, boards[currentMove]);
11336 CopyPlayerNameIntoFileName(dest, src)
11339 while (*src != NULLCHAR && *src != ',') {
11344 *(*dest)++ = *src++;
11349 char *DefaultFileName(ext)
11352 static char def[MSG_SIZ];
11355 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11357 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11359 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11361 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11368 /* Save the current game to the given file */
11370 SaveGameToFile(filename, append)
11378 if (strcmp(filename, "-") == 0) {
11379 return SaveGame(stdout, 0, NULL);
11381 f = fopen(filename, append ? "a" : "w");
11383 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11384 DisplayError(buf, errno);
11387 safeStrCpy(buf, lastMsg, MSG_SIZ);
11388 DisplayMessage(_("Waiting for access to save file"), "");
11389 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11390 DisplayMessage(_("Saving game"), "");
11391 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11392 result = SaveGame(f, 0, NULL);
11393 DisplayMessage(buf, "");
11403 static char buf[MSG_SIZ];
11406 p = strchr(str, ' ');
11407 if (p == NULL) return str;
11408 strncpy(buf, str, p - str);
11409 buf[p - str] = NULLCHAR;
11413 #define PGN_MAX_LINE 75
11415 #define PGN_SIDE_WHITE 0
11416 #define PGN_SIDE_BLACK 1
11419 static int FindFirstMoveOutOfBook( int side )
11423 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11424 int index = backwardMostMove;
11425 int has_book_hit = 0;
11427 if( (index % 2) != side ) {
11431 while( index < forwardMostMove ) {
11432 /* Check to see if engine is in book */
11433 int depth = pvInfoList[index].depth;
11434 int score = pvInfoList[index].score;
11440 else if( score == 0 && depth == 63 ) {
11441 in_book = 1; /* Zappa */
11443 else if( score == 2 && depth == 99 ) {
11444 in_book = 1; /* Abrok */
11447 has_book_hit += in_book;
11463 void GetOutOfBookInfo( char * buf )
11467 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11469 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11470 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11474 if( oob[0] >= 0 || oob[1] >= 0 ) {
11475 for( i=0; i<2; i++ ) {
11479 if( i > 0 && oob[0] >= 0 ) {
11480 strcat( buf, " " );
11483 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11484 sprintf( buf+strlen(buf), "%s%.2f",
11485 pvInfoList[idx].score >= 0 ? "+" : "",
11486 pvInfoList[idx].score / 100.0 );
11492 /* Save game in PGN style and close the file */
11497 int i, offset, linelen, newblock;
11501 int movelen, numlen, blank;
11502 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11504 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11506 tm = time((time_t *) NULL);
11508 PrintPGNTags(f, &gameInfo);
11510 if (backwardMostMove > 0 || startedFromSetupPosition) {
11511 char *fen = PositionToFEN(backwardMostMove, NULL);
11512 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11513 fprintf(f, "\n{--------------\n");
11514 PrintPosition(f, backwardMostMove);
11515 fprintf(f, "--------------}\n");
11519 /* [AS] Out of book annotation */
11520 if( appData.saveOutOfBookInfo ) {
11523 GetOutOfBookInfo( buf );
11525 if( buf[0] != '\0' ) {
11526 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11533 i = backwardMostMove;
11537 while (i < forwardMostMove) {
11538 /* Print comments preceding this move */
11539 if (commentList[i] != NULL) {
11540 if (linelen > 0) fprintf(f, "\n");
11541 fprintf(f, "%s", commentList[i]);
11546 /* Format move number */
11548 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11551 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11553 numtext[0] = NULLCHAR;
11555 numlen = strlen(numtext);
11558 /* Print move number */
11559 blank = linelen > 0 && numlen > 0;
11560 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11569 fprintf(f, "%s", numtext);
11573 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11574 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11577 blank = linelen > 0 && movelen > 0;
11578 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11587 fprintf(f, "%s", move_buffer);
11588 linelen += movelen;
11590 /* [AS] Add PV info if present */
11591 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11592 /* [HGM] add time */
11593 char buf[MSG_SIZ]; int seconds;
11595 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11601 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11604 seconds = (seconds + 4)/10; // round to full seconds
11606 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11608 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11611 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11612 pvInfoList[i].score >= 0 ? "+" : "",
11613 pvInfoList[i].score / 100.0,
11614 pvInfoList[i].depth,
11617 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11619 /* Print score/depth */
11620 blank = linelen > 0 && movelen > 0;
11621 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11630 fprintf(f, "%s", move_buffer);
11631 linelen += movelen;
11637 /* Start a new line */
11638 if (linelen > 0) fprintf(f, "\n");
11640 /* Print comments after last move */
11641 if (commentList[i] != NULL) {
11642 fprintf(f, "%s\n", commentList[i]);
11646 if (gameInfo.resultDetails != NULL &&
11647 gameInfo.resultDetails[0] != NULLCHAR) {
11648 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11649 PGNResult(gameInfo.result));
11651 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11655 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11659 /* Save game in old style and close the file */
11661 SaveGameOldStyle(f)
11667 tm = time((time_t *) NULL);
11669 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11672 if (backwardMostMove > 0 || startedFromSetupPosition) {
11673 fprintf(f, "\n[--------------\n");
11674 PrintPosition(f, backwardMostMove);
11675 fprintf(f, "--------------]\n");
11680 i = backwardMostMove;
11681 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11683 while (i < forwardMostMove) {
11684 if (commentList[i] != NULL) {
11685 fprintf(f, "[%s]\n", commentList[i]);
11688 if ((i % 2) == 1) {
11689 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11692 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11694 if (commentList[i] != NULL) {
11698 if (i >= forwardMostMove) {
11702 fprintf(f, "%s\n", parseList[i]);
11707 if (commentList[i] != NULL) {
11708 fprintf(f, "[%s]\n", commentList[i]);
11711 /* This isn't really the old style, but it's close enough */
11712 if (gameInfo.resultDetails != NULL &&
11713 gameInfo.resultDetails[0] != NULLCHAR) {
11714 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11715 gameInfo.resultDetails);
11717 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11724 /* Save the current game to open file f and close the file */
11726 SaveGame(f, dummy, dummy2)
11731 if (gameMode == EditPosition) EditPositionDone(TRUE);
11732 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11733 if (appData.oldSaveStyle)
11734 return SaveGameOldStyle(f);
11736 return SaveGamePGN(f);
11739 /* Save the current position to the given file */
11741 SavePositionToFile(filename)
11747 if (strcmp(filename, "-") == 0) {
11748 return SavePosition(stdout, 0, NULL);
11750 f = fopen(filename, "a");
11752 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11753 DisplayError(buf, errno);
11756 safeStrCpy(buf, lastMsg, MSG_SIZ);
11757 DisplayMessage(_("Waiting for access to save file"), "");
11758 flock(fileno(f), LOCK_EX); // [HGM] lock
11759 DisplayMessage(_("Saving position"), "");
11760 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11761 SavePosition(f, 0, NULL);
11762 DisplayMessage(buf, "");
11768 /* Save the current position to the given open file and close the file */
11770 SavePosition(f, dummy, dummy2)
11778 if (gameMode == EditPosition) EditPositionDone(TRUE);
11779 if (appData.oldSaveStyle) {
11780 tm = time((time_t *) NULL);
11782 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11784 fprintf(f, "[--------------\n");
11785 PrintPosition(f, currentMove);
11786 fprintf(f, "--------------]\n");
11788 fen = PositionToFEN(currentMove, NULL);
11789 fprintf(f, "%s\n", fen);
11797 ReloadCmailMsgEvent(unregister)
11801 static char *inFilename = NULL;
11802 static char *outFilename;
11804 struct stat inbuf, outbuf;
11807 /* Any registered moves are unregistered if unregister is set, */
11808 /* i.e. invoked by the signal handler */
11810 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11811 cmailMoveRegistered[i] = FALSE;
11812 if (cmailCommentList[i] != NULL) {
11813 free(cmailCommentList[i]);
11814 cmailCommentList[i] = NULL;
11817 nCmailMovesRegistered = 0;
11820 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11821 cmailResult[i] = CMAIL_NOT_RESULT;
11825 if (inFilename == NULL) {
11826 /* Because the filenames are static they only get malloced once */
11827 /* and they never get freed */
11828 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11829 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11831 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11832 sprintf(outFilename, "%s.out", appData.cmailGameName);
11835 status = stat(outFilename, &outbuf);
11837 cmailMailedMove = FALSE;
11839 status = stat(inFilename, &inbuf);
11840 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11843 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11844 counts the games, notes how each one terminated, etc.
11846 It would be nice to remove this kludge and instead gather all
11847 the information while building the game list. (And to keep it
11848 in the game list nodes instead of having a bunch of fixed-size
11849 parallel arrays.) Note this will require getting each game's
11850 termination from the PGN tags, as the game list builder does
11851 not process the game moves. --mann
11853 cmailMsgLoaded = TRUE;
11854 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11856 /* Load first game in the file or popup game menu */
11857 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11859 #endif /* !WIN32 */
11867 char string[MSG_SIZ];
11869 if ( cmailMailedMove
11870 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11871 return TRUE; /* Allow free viewing */
11874 /* Unregister move to ensure that we don't leave RegisterMove */
11875 /* with the move registered when the conditions for registering no */
11877 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11878 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11879 nCmailMovesRegistered --;
11881 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11883 free(cmailCommentList[lastLoadGameNumber - 1]);
11884 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11888 if (cmailOldMove == -1) {
11889 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11893 if (currentMove > cmailOldMove + 1) {
11894 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11898 if (currentMove < cmailOldMove) {
11899 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11903 if (forwardMostMove > currentMove) {
11904 /* Silently truncate extra moves */
11908 if ( (currentMove == cmailOldMove + 1)
11909 || ( (currentMove == cmailOldMove)
11910 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11911 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11912 if (gameInfo.result != GameUnfinished) {
11913 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11916 if (commentList[currentMove] != NULL) {
11917 cmailCommentList[lastLoadGameNumber - 1]
11918 = StrSave(commentList[currentMove]);
11920 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11922 if (appData.debugMode)
11923 fprintf(debugFP, "Saving %s for game %d\n",
11924 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11926 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11928 f = fopen(string, "w");
11929 if (appData.oldSaveStyle) {
11930 SaveGameOldStyle(f); /* also closes the file */
11932 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11933 f = fopen(string, "w");
11934 SavePosition(f, 0, NULL); /* also closes the file */
11936 fprintf(f, "{--------------\n");
11937 PrintPosition(f, currentMove);
11938 fprintf(f, "--------------}\n\n");
11940 SaveGame(f, 0, NULL); /* also closes the file*/
11943 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11944 nCmailMovesRegistered ++;
11945 } else if (nCmailGames == 1) {
11946 DisplayError(_("You have not made a move yet"), 0);
11957 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11958 FILE *commandOutput;
11959 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11960 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11966 if (! cmailMsgLoaded) {
11967 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11971 if (nCmailGames == nCmailResults) {
11972 DisplayError(_("No unfinished games"), 0);
11976 #if CMAIL_PROHIBIT_REMAIL
11977 if (cmailMailedMove) {
11978 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);
11979 DisplayError(msg, 0);
11984 if (! (cmailMailedMove || RegisterMove())) return;
11986 if ( cmailMailedMove
11987 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11988 snprintf(string, MSG_SIZ, partCommandString,
11989 appData.debugMode ? " -v" : "", appData.cmailGameName);
11990 commandOutput = popen(string, "r");
11992 if (commandOutput == NULL) {
11993 DisplayError(_("Failed to invoke cmail"), 0);
11995 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11996 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11998 if (nBuffers > 1) {
11999 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12000 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12001 nBytes = MSG_SIZ - 1;
12003 (void) memcpy(msg, buffer, nBytes);
12005 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12007 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12008 cmailMailedMove = TRUE; /* Prevent >1 moves */
12011 for (i = 0; i < nCmailGames; i ++) {
12012 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12017 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12019 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12021 appData.cmailGameName,
12023 LoadGameFromFile(buffer, 1, buffer, FALSE);
12024 cmailMsgLoaded = FALSE;
12028 DisplayInformation(msg);
12029 pclose(commandOutput);
12032 if ((*cmailMsg) != '\0') {
12033 DisplayInformation(cmailMsg);
12038 #endif /* !WIN32 */
12047 int prependComma = 0;
12049 char string[MSG_SIZ]; /* Space for game-list */
12052 if (!cmailMsgLoaded) return "";
12054 if (cmailMailedMove) {
12055 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12057 /* Create a list of games left */
12058 snprintf(string, MSG_SIZ, "[");
12059 for (i = 0; i < nCmailGames; i ++) {
12060 if (! ( cmailMoveRegistered[i]
12061 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12062 if (prependComma) {
12063 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12065 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12069 strcat(string, number);
12072 strcat(string, "]");
12074 if (nCmailMovesRegistered + nCmailResults == 0) {
12075 switch (nCmailGames) {
12077 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12081 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12085 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12090 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12092 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12097 if (nCmailResults == nCmailGames) {
12098 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12100 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12105 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12117 if (gameMode == Training)
12118 SetTrainingModeOff();
12121 cmailMsgLoaded = FALSE;
12122 if (appData.icsActive) {
12123 SendToICS(ics_prefix);
12124 SendToICS("refresh\n");
12134 /* Give up on clean exit */
12138 /* Keep trying for clean exit */
12142 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12144 if (telnetISR != NULL) {
12145 RemoveInputSource(telnetISR);
12147 if (icsPR != NoProc) {
12148 DestroyChildProcess(icsPR, TRUE);
12151 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12152 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12154 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12155 /* make sure this other one finishes before killing it! */
12156 if(endingGame) { int count = 0;
12157 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12158 while(endingGame && count++ < 10) DoSleep(1);
12159 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12162 /* Kill off chess programs */
12163 if (first.pr != NoProc) {
12166 DoSleep( appData.delayBeforeQuit );
12167 SendToProgram("quit\n", &first);
12168 DoSleep( appData.delayAfterQuit );
12169 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12171 if (second.pr != NoProc) {
12172 DoSleep( appData.delayBeforeQuit );
12173 SendToProgram("quit\n", &second);
12174 DoSleep( appData.delayAfterQuit );
12175 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12177 if (first.isr != NULL) {
12178 RemoveInputSource(first.isr);
12180 if (second.isr != NULL) {
12181 RemoveInputSource(second.isr);
12184 ShutDownFrontEnd();
12191 if (appData.debugMode)
12192 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12196 if (gameMode == MachinePlaysWhite ||
12197 gameMode == MachinePlaysBlack) {
12200 DisplayBothClocks();
12202 if (gameMode == PlayFromGameFile) {
12203 if (appData.timeDelay >= 0)
12204 AutoPlayGameLoop();
12205 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12206 Reset(FALSE, TRUE);
12207 SendToICS(ics_prefix);
12208 SendToICS("refresh\n");
12209 } else if (currentMove < forwardMostMove) {
12210 ForwardInner(forwardMostMove);
12212 pauseExamInvalid = FALSE;
12214 switch (gameMode) {
12218 pauseExamForwardMostMove = forwardMostMove;
12219 pauseExamInvalid = FALSE;
12222 case IcsPlayingWhite:
12223 case IcsPlayingBlack:
12227 case PlayFromGameFile:
12228 (void) StopLoadGameTimer();
12232 case BeginningOfGame:
12233 if (appData.icsActive) return;
12234 /* else fall through */
12235 case MachinePlaysWhite:
12236 case MachinePlaysBlack:
12237 case TwoMachinesPlay:
12238 if (forwardMostMove == 0)
12239 return; /* don't pause if no one has moved */
12240 if ((gameMode == MachinePlaysWhite &&
12241 !WhiteOnMove(forwardMostMove)) ||
12242 (gameMode == MachinePlaysBlack &&
12243 WhiteOnMove(forwardMostMove))) {
12256 char title[MSG_SIZ];
12258 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12259 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12261 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12262 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12263 parseList[currentMove - 1]);
12266 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12273 char *tags = PGNTags(&gameInfo);
12274 EditTagsPopUp(tags, NULL);
12281 if (appData.noChessProgram || gameMode == AnalyzeMode)
12284 if (gameMode != AnalyzeFile) {
12285 if (!appData.icsEngineAnalyze) {
12287 if (gameMode != EditGame) return;
12289 ResurrectChessProgram();
12290 SendToProgram("analyze\n", &first);
12291 first.analyzing = TRUE;
12292 /*first.maybeThinking = TRUE;*/
12293 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12294 EngineOutputPopUp();
12296 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12301 StartAnalysisClock();
12302 GetTimeMark(&lastNodeCountTime);
12309 if (appData.noChessProgram || gameMode == AnalyzeFile)
12312 if (gameMode != AnalyzeMode) {
12314 if (gameMode != EditGame) return;
12315 ResurrectChessProgram();
12316 SendToProgram("analyze\n", &first);
12317 first.analyzing = TRUE;
12318 /*first.maybeThinking = TRUE;*/
12319 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12320 EngineOutputPopUp();
12322 gameMode = AnalyzeFile;
12327 StartAnalysisClock();
12328 GetTimeMark(&lastNodeCountTime);
12333 MachineWhiteEvent()
12336 char *bookHit = NULL;
12338 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12342 if (gameMode == PlayFromGameFile ||
12343 gameMode == TwoMachinesPlay ||
12344 gameMode == Training ||
12345 gameMode == AnalyzeMode ||
12346 gameMode == EndOfGame)
12349 if (gameMode == EditPosition)
12350 EditPositionDone(TRUE);
12352 if (!WhiteOnMove(currentMove)) {
12353 DisplayError(_("It is not White's turn"), 0);
12357 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12360 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12361 gameMode == AnalyzeFile)
12364 ResurrectChessProgram(); /* in case it isn't running */
12365 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12366 gameMode = MachinePlaysWhite;
12369 gameMode = MachinePlaysWhite;
12373 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12375 if (first.sendName) {
12376 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12377 SendToProgram(buf, &first);
12379 if (first.sendTime) {
12380 if (first.useColors) {
12381 SendToProgram("black\n", &first); /*gnu kludge*/
12383 SendTimeRemaining(&first, TRUE);
12385 if (first.useColors) {
12386 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12388 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12389 SetMachineThinkingEnables();
12390 first.maybeThinking = TRUE;
12394 if (appData.autoFlipView && !flipView) {
12395 flipView = !flipView;
12396 DrawPosition(FALSE, NULL);
12397 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12400 if(bookHit) { // [HGM] book: simulate book reply
12401 static char bookMove[MSG_SIZ]; // a bit generous?
12403 programStats.nodes = programStats.depth = programStats.time =
12404 programStats.score = programStats.got_only_move = 0;
12405 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12407 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12408 strcat(bookMove, bookHit);
12409 HandleMachineMove(bookMove, &first);
12414 MachineBlackEvent()
12417 char *bookHit = NULL;
12419 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12423 if (gameMode == PlayFromGameFile ||
12424 gameMode == TwoMachinesPlay ||
12425 gameMode == Training ||
12426 gameMode == AnalyzeMode ||
12427 gameMode == EndOfGame)
12430 if (gameMode == EditPosition)
12431 EditPositionDone(TRUE);
12433 if (WhiteOnMove(currentMove)) {
12434 DisplayError(_("It is not Black's turn"), 0);
12438 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12441 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12442 gameMode == AnalyzeFile)
12445 ResurrectChessProgram(); /* in case it isn't running */
12446 gameMode = MachinePlaysBlack;
12450 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12452 if (first.sendName) {
12453 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12454 SendToProgram(buf, &first);
12456 if (first.sendTime) {
12457 if (first.useColors) {
12458 SendToProgram("white\n", &first); /*gnu kludge*/
12460 SendTimeRemaining(&first, FALSE);
12462 if (first.useColors) {
12463 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12465 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12466 SetMachineThinkingEnables();
12467 first.maybeThinking = TRUE;
12470 if (appData.autoFlipView && flipView) {
12471 flipView = !flipView;
12472 DrawPosition(FALSE, NULL);
12473 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12475 if(bookHit) { // [HGM] book: simulate book reply
12476 static char bookMove[MSG_SIZ]; // a bit generous?
12478 programStats.nodes = programStats.depth = programStats.time =
12479 programStats.score = programStats.got_only_move = 0;
12480 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12482 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12483 strcat(bookMove, bookHit);
12484 HandleMachineMove(bookMove, &first);
12490 DisplayTwoMachinesTitle()
12493 if (appData.matchGames > 0) {
12494 if (first.twoMachinesColor[0] == 'w') {
12495 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12496 gameInfo.white, gameInfo.black,
12497 first.matchWins, second.matchWins,
12498 matchGame - 1 - (first.matchWins + second.matchWins));
12500 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12501 gameInfo.white, gameInfo.black,
12502 second.matchWins, first.matchWins,
12503 matchGame - 1 - (first.matchWins + second.matchWins));
12506 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12512 SettingsMenuIfReady()
12514 if (second.lastPing != second.lastPong) {
12515 DisplayMessage("", _("Waiting for second chess program"));
12516 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12520 DisplayMessage("", "");
12521 SettingsPopUp(&second);
12525 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12528 if (cps->pr == NULL) {
12529 StartChessProgram(cps);
12530 if (cps->protocolVersion == 1) {
12533 /* kludge: allow timeout for initial "feature" command */
12535 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12536 DisplayMessage("", buf);
12537 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12545 TwoMachinesEvent P((void))
12549 ChessProgramState *onmove;
12550 char *bookHit = NULL;
12551 static int stalling = 0;
12555 if (appData.noChessProgram) return;
12557 switch (gameMode) {
12558 case TwoMachinesPlay:
12560 case MachinePlaysWhite:
12561 case MachinePlaysBlack:
12562 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12563 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12567 case BeginningOfGame:
12568 case PlayFromGameFile:
12571 if (gameMode != EditGame) return;
12574 EditPositionDone(TRUE);
12585 // forwardMostMove = currentMove;
12586 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12588 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12590 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12591 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12592 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12596 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12597 SendToProgram("force\n", &second);
12599 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12602 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12603 if(appData.matchPause>10000 || appData.matchPause<10)
12604 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12605 wait = SubtractTimeMarks(&now, &pauseStart);
12606 if(wait < appData.matchPause) {
12607 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12611 DisplayMessage("", "");
12612 if (startedFromSetupPosition) {
12613 SendBoard(&second, backwardMostMove);
12614 if (appData.debugMode) {
12615 fprintf(debugFP, "Two Machines\n");
12618 for (i = backwardMostMove; i < forwardMostMove; i++) {
12619 SendMoveToProgram(i, &second);
12622 gameMode = TwoMachinesPlay;
12626 DisplayTwoMachinesTitle();
12628 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12633 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12634 SendToProgram(first.computerString, &first);
12635 if (first.sendName) {
12636 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12637 SendToProgram(buf, &first);
12639 SendToProgram(second.computerString, &second);
12640 if (second.sendName) {
12641 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12642 SendToProgram(buf, &second);
12646 if (!first.sendTime || !second.sendTime) {
12647 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12648 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12650 if (onmove->sendTime) {
12651 if (onmove->useColors) {
12652 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12654 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12656 if (onmove->useColors) {
12657 SendToProgram(onmove->twoMachinesColor, onmove);
12659 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12660 // SendToProgram("go\n", onmove);
12661 onmove->maybeThinking = TRUE;
12662 SetMachineThinkingEnables();
12666 if(bookHit) { // [HGM] book: simulate book reply
12667 static char bookMove[MSG_SIZ]; // a bit generous?
12669 programStats.nodes = programStats.depth = programStats.time =
12670 programStats.score = programStats.got_only_move = 0;
12671 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12673 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12674 strcat(bookMove, bookHit);
12675 savedMessage = bookMove; // args for deferred call
12676 savedState = onmove;
12677 ScheduleDelayedEvent(DeferredBookMove, 1);
12684 if (gameMode == Training) {
12685 SetTrainingModeOff();
12686 gameMode = PlayFromGameFile;
12687 DisplayMessage("", _("Training mode off"));
12689 gameMode = Training;
12690 animateTraining = appData.animate;
12692 /* make sure we are not already at the end of the game */
12693 if (currentMove < forwardMostMove) {
12694 SetTrainingModeOn();
12695 DisplayMessage("", _("Training mode on"));
12697 gameMode = PlayFromGameFile;
12698 DisplayError(_("Already at end of game"), 0);
12707 if (!appData.icsActive) return;
12708 switch (gameMode) {
12709 case IcsPlayingWhite:
12710 case IcsPlayingBlack:
12713 case BeginningOfGame:
12721 EditPositionDone(TRUE);
12734 gameMode = IcsIdle;
12745 switch (gameMode) {
12747 SetTrainingModeOff();
12749 case MachinePlaysWhite:
12750 case MachinePlaysBlack:
12751 case BeginningOfGame:
12752 SendToProgram("force\n", &first);
12753 SetUserThinkingEnables();
12755 case PlayFromGameFile:
12756 (void) StopLoadGameTimer();
12757 if (gameFileFP != NULL) {
12762 EditPositionDone(TRUE);
12767 SendToProgram("force\n", &first);
12769 case TwoMachinesPlay:
12770 GameEnds(EndOfFile, NULL, GE_PLAYER);
12771 ResurrectChessProgram();
12772 SetUserThinkingEnables();
12775 ResurrectChessProgram();
12777 case IcsPlayingBlack:
12778 case IcsPlayingWhite:
12779 DisplayError(_("Warning: You are still playing a game"), 0);
12782 DisplayError(_("Warning: You are still observing a game"), 0);
12785 DisplayError(_("Warning: You are still examining a game"), 0);
12796 first.offeredDraw = second.offeredDraw = 0;
12798 if (gameMode == PlayFromGameFile) {
12799 whiteTimeRemaining = timeRemaining[0][currentMove];
12800 blackTimeRemaining = timeRemaining[1][currentMove];
12804 if (gameMode == MachinePlaysWhite ||
12805 gameMode == MachinePlaysBlack ||
12806 gameMode == TwoMachinesPlay ||
12807 gameMode == EndOfGame) {
12808 i = forwardMostMove;
12809 while (i > currentMove) {
12810 SendToProgram("undo\n", &first);
12813 whiteTimeRemaining = timeRemaining[0][currentMove];
12814 blackTimeRemaining = timeRemaining[1][currentMove];
12815 DisplayBothClocks();
12816 if (whiteFlag || blackFlag) {
12817 whiteFlag = blackFlag = 0;
12822 gameMode = EditGame;
12829 EditPositionEvent()
12831 if (gameMode == EditPosition) {
12837 if (gameMode != EditGame) return;
12839 gameMode = EditPosition;
12842 if (currentMove > 0)
12843 CopyBoard(boards[0], boards[currentMove]);
12845 blackPlaysFirst = !WhiteOnMove(currentMove);
12847 currentMove = forwardMostMove = backwardMostMove = 0;
12848 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12855 /* [DM] icsEngineAnalyze - possible call from other functions */
12856 if (appData.icsEngineAnalyze) {
12857 appData.icsEngineAnalyze = FALSE;
12859 DisplayMessage("",_("Close ICS engine analyze..."));
12861 if (first.analysisSupport && first.analyzing) {
12862 SendToProgram("exit\n", &first);
12863 first.analyzing = FALSE;
12865 thinkOutput[0] = NULLCHAR;
12869 EditPositionDone(Boolean fakeRights)
12871 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12873 startedFromSetupPosition = TRUE;
12874 InitChessProgram(&first, FALSE);
12875 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12876 boards[0][EP_STATUS] = EP_NONE;
12877 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12878 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12879 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12880 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12881 } else boards[0][CASTLING][2] = NoRights;
12882 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12883 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12884 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12885 } else boards[0][CASTLING][5] = NoRights;
12887 SendToProgram("force\n", &first);
12888 if (blackPlaysFirst) {
12889 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12890 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12891 currentMove = forwardMostMove = backwardMostMove = 1;
12892 CopyBoard(boards[1], boards[0]);
12894 currentMove = forwardMostMove = backwardMostMove = 0;
12896 SendBoard(&first, forwardMostMove);
12897 if (appData.debugMode) {
12898 fprintf(debugFP, "EditPosDone\n");
12901 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12902 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12903 gameMode = EditGame;
12905 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12906 ClearHighlights(); /* [AS] */
12909 /* Pause for `ms' milliseconds */
12910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12920 } while (SubtractTimeMarks(&m2, &m1) < ms);
12923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12925 SendMultiLineToICS(buf)
12928 char temp[MSG_SIZ+1], *p;
12935 strncpy(temp, buf, len);
12940 if (*p == '\n' || *p == '\r')
12945 strcat(temp, "\n");
12947 SendToPlayer(temp, strlen(temp));
12951 SetWhiteToPlayEvent()
12953 if (gameMode == EditPosition) {
12954 blackPlaysFirst = FALSE;
12955 DisplayBothClocks(); /* works because currentMove is 0 */
12956 } else if (gameMode == IcsExamining) {
12957 SendToICS(ics_prefix);
12958 SendToICS("tomove white\n");
12963 SetBlackToPlayEvent()
12965 if (gameMode == EditPosition) {
12966 blackPlaysFirst = TRUE;
12967 currentMove = 1; /* kludge */
12968 DisplayBothClocks();
12970 } else if (gameMode == IcsExamining) {
12971 SendToICS(ics_prefix);
12972 SendToICS("tomove black\n");
12977 EditPositionMenuEvent(selection, x, y)
12978 ChessSquare selection;
12982 ChessSquare piece = boards[0][y][x];
12984 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12986 switch (selection) {
12988 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12989 SendToICS(ics_prefix);
12990 SendToICS("bsetup clear\n");
12991 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12992 SendToICS(ics_prefix);
12993 SendToICS("clearboard\n");
12995 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12996 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12997 for (y = 0; y < BOARD_HEIGHT; y++) {
12998 if (gameMode == IcsExamining) {
12999 if (boards[currentMove][y][x] != EmptySquare) {
13000 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13005 boards[0][y][x] = p;
13010 if (gameMode == EditPosition) {
13011 DrawPosition(FALSE, boards[0]);
13016 SetWhiteToPlayEvent();
13020 SetBlackToPlayEvent();
13024 if (gameMode == IcsExamining) {
13025 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13026 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13029 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13030 if(x == BOARD_LEFT-2) {
13031 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13032 boards[0][y][1] = 0;
13034 if(x == BOARD_RGHT+1) {
13035 if(y >= gameInfo.holdingsSize) break;
13036 boards[0][y][BOARD_WIDTH-2] = 0;
13039 boards[0][y][x] = EmptySquare;
13040 DrawPosition(FALSE, boards[0]);
13045 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13046 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13047 selection = (ChessSquare) (PROMOTED piece);
13048 } else if(piece == EmptySquare) selection = WhiteSilver;
13049 else selection = (ChessSquare)((int)piece - 1);
13053 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13054 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13055 selection = (ChessSquare) (DEMOTED piece);
13056 } else if(piece == EmptySquare) selection = BlackSilver;
13057 else selection = (ChessSquare)((int)piece + 1);
13062 if(gameInfo.variant == VariantShatranj ||
13063 gameInfo.variant == VariantXiangqi ||
13064 gameInfo.variant == VariantCourier ||
13065 gameInfo.variant == VariantMakruk )
13066 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13071 if(gameInfo.variant == VariantXiangqi)
13072 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13073 if(gameInfo.variant == VariantKnightmate)
13074 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13077 if (gameMode == IcsExamining) {
13078 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13079 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13080 PieceToChar(selection), AAA + x, ONE + y);
13083 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13085 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13086 n = PieceToNumber(selection - BlackPawn);
13087 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13088 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13089 boards[0][BOARD_HEIGHT-1-n][1]++;
13091 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13092 n = PieceToNumber(selection);
13093 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13094 boards[0][n][BOARD_WIDTH-1] = selection;
13095 boards[0][n][BOARD_WIDTH-2]++;
13098 boards[0][y][x] = selection;
13099 DrawPosition(TRUE, boards[0]);
13107 DropMenuEvent(selection, x, y)
13108 ChessSquare selection;
13111 ChessMove moveType;
13113 switch (gameMode) {
13114 case IcsPlayingWhite:
13115 case MachinePlaysBlack:
13116 if (!WhiteOnMove(currentMove)) {
13117 DisplayMoveError(_("It is Black's turn"));
13120 moveType = WhiteDrop;
13122 case IcsPlayingBlack:
13123 case MachinePlaysWhite:
13124 if (WhiteOnMove(currentMove)) {
13125 DisplayMoveError(_("It is White's turn"));
13128 moveType = BlackDrop;
13131 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13137 if (moveType == BlackDrop && selection < BlackPawn) {
13138 selection = (ChessSquare) ((int) selection
13139 + (int) BlackPawn - (int) WhitePawn);
13141 if (boards[currentMove][y][x] != EmptySquare) {
13142 DisplayMoveError(_("That square is occupied"));
13146 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13152 /* Accept a pending offer of any kind from opponent */
13154 if (appData.icsActive) {
13155 SendToICS(ics_prefix);
13156 SendToICS("accept\n");
13157 } else if (cmailMsgLoaded) {
13158 if (currentMove == cmailOldMove &&
13159 commentList[cmailOldMove] != NULL &&
13160 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13161 "Black offers a draw" : "White offers a draw")) {
13163 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13164 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13166 DisplayError(_("There is no pending offer on this move"), 0);
13167 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13170 /* Not used for offers from chess program */
13177 /* Decline a pending offer of any kind from opponent */
13179 if (appData.icsActive) {
13180 SendToICS(ics_prefix);
13181 SendToICS("decline\n");
13182 } else if (cmailMsgLoaded) {
13183 if (currentMove == cmailOldMove &&
13184 commentList[cmailOldMove] != NULL &&
13185 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13186 "Black offers a draw" : "White offers a draw")) {
13188 AppendComment(cmailOldMove, "Draw declined", TRUE);
13189 DisplayComment(cmailOldMove - 1, "Draw declined");
13192 DisplayError(_("There is no pending offer on this move"), 0);
13195 /* Not used for offers from chess program */
13202 /* Issue ICS rematch command */
13203 if (appData.icsActive) {
13204 SendToICS(ics_prefix);
13205 SendToICS("rematch\n");
13212 /* Call your opponent's flag (claim a win on time) */
13213 if (appData.icsActive) {
13214 SendToICS(ics_prefix);
13215 SendToICS("flag\n");
13217 switch (gameMode) {
13220 case MachinePlaysWhite:
13223 GameEnds(GameIsDrawn, "Both players ran out of time",
13226 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13228 DisplayError(_("Your opponent is not out of time"), 0);
13231 case MachinePlaysBlack:
13234 GameEnds(GameIsDrawn, "Both players ran out of time",
13237 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13239 DisplayError(_("Your opponent is not out of time"), 0);
13247 ClockClick(int which)
13248 { // [HGM] code moved to back-end from winboard.c
13249 if(which) { // black clock
13250 if (gameMode == EditPosition || gameMode == IcsExamining) {
13251 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13252 SetBlackToPlayEvent();
13253 } else if (gameMode == EditGame || shiftKey) {
13254 AdjustClock(which, -1);
13255 } else if (gameMode == IcsPlayingWhite ||
13256 gameMode == MachinePlaysBlack) {
13259 } else { // white clock
13260 if (gameMode == EditPosition || gameMode == IcsExamining) {
13261 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13262 SetWhiteToPlayEvent();
13263 } else if (gameMode == EditGame || shiftKey) {
13264 AdjustClock(which, -1);
13265 } else if (gameMode == IcsPlayingBlack ||
13266 gameMode == MachinePlaysWhite) {
13275 /* Offer draw or accept pending draw offer from opponent */
13277 if (appData.icsActive) {
13278 /* Note: tournament rules require draw offers to be
13279 made after you make your move but before you punch
13280 your clock. Currently ICS doesn't let you do that;
13281 instead, you immediately punch your clock after making
13282 a move, but you can offer a draw at any time. */
13284 SendToICS(ics_prefix);
13285 SendToICS("draw\n");
13286 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13287 } else if (cmailMsgLoaded) {
13288 if (currentMove == cmailOldMove &&
13289 commentList[cmailOldMove] != NULL &&
13290 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13291 "Black offers a draw" : "White offers a draw")) {
13292 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13293 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13294 } else if (currentMove == cmailOldMove + 1) {
13295 char *offer = WhiteOnMove(cmailOldMove) ?
13296 "White offers a draw" : "Black offers a draw";
13297 AppendComment(currentMove, offer, TRUE);
13298 DisplayComment(currentMove - 1, offer);
13299 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13301 DisplayError(_("You must make your move before offering a draw"), 0);
13302 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13304 } else if (first.offeredDraw) {
13305 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13307 if (first.sendDrawOffers) {
13308 SendToProgram("draw\n", &first);
13309 userOfferedDraw = TRUE;
13317 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13319 if (appData.icsActive) {
13320 SendToICS(ics_prefix);
13321 SendToICS("adjourn\n");
13323 /* Currently GNU Chess doesn't offer or accept Adjourns */
13331 /* Offer Abort or accept pending Abort offer from opponent */
13333 if (appData.icsActive) {
13334 SendToICS(ics_prefix);
13335 SendToICS("abort\n");
13337 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13344 /* Resign. You can do this even if it's not your turn. */
13346 if (appData.icsActive) {
13347 SendToICS(ics_prefix);
13348 SendToICS("resign\n");
13350 switch (gameMode) {
13351 case MachinePlaysWhite:
13352 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13354 case MachinePlaysBlack:
13355 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13358 if (cmailMsgLoaded) {
13360 if (WhiteOnMove(cmailOldMove)) {
13361 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13363 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13365 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13376 StopObservingEvent()
13378 /* Stop observing current games */
13379 SendToICS(ics_prefix);
13380 SendToICS("unobserve\n");
13384 StopExaminingEvent()
13386 /* Stop observing current game */
13387 SendToICS(ics_prefix);
13388 SendToICS("unexamine\n");
13392 ForwardInner(target)
13397 if (appData.debugMode)
13398 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13399 target, currentMove, forwardMostMove);
13401 if (gameMode == EditPosition)
13404 if (gameMode == PlayFromGameFile && !pausing)
13407 if (gameMode == IcsExamining && pausing)
13408 limit = pauseExamForwardMostMove;
13410 limit = forwardMostMove;
13412 if (target > limit) target = limit;
13414 if (target > 0 && moveList[target - 1][0]) {
13415 int fromX, fromY, toX, toY;
13416 toX = moveList[target - 1][2] - AAA;
13417 toY = moveList[target - 1][3] - ONE;
13418 if (moveList[target - 1][1] == '@') {
13419 if (appData.highlightLastMove) {
13420 SetHighlights(-1, -1, toX, toY);
13423 fromX = moveList[target - 1][0] - AAA;
13424 fromY = moveList[target - 1][1] - ONE;
13425 if (target == currentMove + 1) {
13426 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13428 if (appData.highlightLastMove) {
13429 SetHighlights(fromX, fromY, toX, toY);
13433 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13434 gameMode == Training || gameMode == PlayFromGameFile ||
13435 gameMode == AnalyzeFile) {
13436 while (currentMove < target) {
13437 SendMoveToProgram(currentMove++, &first);
13440 currentMove = target;
13443 if (gameMode == EditGame || gameMode == EndOfGame) {
13444 whiteTimeRemaining = timeRemaining[0][currentMove];
13445 blackTimeRemaining = timeRemaining[1][currentMove];
13447 DisplayBothClocks();
13448 DisplayMove(currentMove - 1);
13449 DrawPosition(FALSE, boards[currentMove]);
13450 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13451 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13452 DisplayComment(currentMove - 1, commentList[currentMove]);
13460 if (gameMode == IcsExamining && !pausing) {
13461 SendToICS(ics_prefix);
13462 SendToICS("forward\n");
13464 ForwardInner(currentMove + 1);
13471 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13472 /* to optimze, we temporarily turn off analysis mode while we feed
13473 * the remaining moves to the engine. Otherwise we get analysis output
13476 if (first.analysisSupport) {
13477 SendToProgram("exit\nforce\n", &first);
13478 first.analyzing = FALSE;
13482 if (gameMode == IcsExamining && !pausing) {
13483 SendToICS(ics_prefix);
13484 SendToICS("forward 999999\n");
13486 ForwardInner(forwardMostMove);
13489 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13490 /* we have fed all the moves, so reactivate analysis mode */
13491 SendToProgram("analyze\n", &first);
13492 first.analyzing = TRUE;
13493 /*first.maybeThinking = TRUE;*/
13494 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13499 BackwardInner(target)
13502 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13504 if (appData.debugMode)
13505 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13506 target, currentMove, forwardMostMove);
13508 if (gameMode == EditPosition) return;
13509 if (currentMove <= backwardMostMove) {
13511 DrawPosition(full_redraw, boards[currentMove]);
13514 if (gameMode == PlayFromGameFile && !pausing)
13517 if (moveList[target][0]) {
13518 int fromX, fromY, toX, toY;
13519 toX = moveList[target][2] - AAA;
13520 toY = moveList[target][3] - ONE;
13521 if (moveList[target][1] == '@') {
13522 if (appData.highlightLastMove) {
13523 SetHighlights(-1, -1, toX, toY);
13526 fromX = moveList[target][0] - AAA;
13527 fromY = moveList[target][1] - ONE;
13528 if (target == currentMove - 1) {
13529 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13531 if (appData.highlightLastMove) {
13532 SetHighlights(fromX, fromY, toX, toY);
13536 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13537 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13538 while (currentMove > target) {
13539 SendToProgram("undo\n", &first);
13543 currentMove = target;
13546 if (gameMode == EditGame || gameMode == EndOfGame) {
13547 whiteTimeRemaining = timeRemaining[0][currentMove];
13548 blackTimeRemaining = timeRemaining[1][currentMove];
13550 DisplayBothClocks();
13551 DisplayMove(currentMove - 1);
13552 DrawPosition(full_redraw, boards[currentMove]);
13553 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13554 // [HGM] PV info: routine tests if comment empty
13555 DisplayComment(currentMove - 1, commentList[currentMove]);
13561 if (gameMode == IcsExamining && !pausing) {
13562 SendToICS(ics_prefix);
13563 SendToICS("backward\n");
13565 BackwardInner(currentMove - 1);
13572 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13573 /* to optimize, we temporarily turn off analysis mode while we undo
13574 * all the moves. Otherwise we get analysis output after each undo.
13576 if (first.analysisSupport) {
13577 SendToProgram("exit\nforce\n", &first);
13578 first.analyzing = FALSE;
13582 if (gameMode == IcsExamining && !pausing) {
13583 SendToICS(ics_prefix);
13584 SendToICS("backward 999999\n");
13586 BackwardInner(backwardMostMove);
13589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13590 /* we have fed all the moves, so reactivate analysis mode */
13591 SendToProgram("analyze\n", &first);
13592 first.analyzing = TRUE;
13593 /*first.maybeThinking = TRUE;*/
13594 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13601 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13602 if (to >= forwardMostMove) to = forwardMostMove;
13603 if (to <= backwardMostMove) to = backwardMostMove;
13604 if (to < currentMove) {
13612 RevertEvent(Boolean annotate)
13614 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13617 if (gameMode != IcsExamining) {
13618 DisplayError(_("You are not examining a game"), 0);
13622 DisplayError(_("You can't revert while pausing"), 0);
13625 SendToICS(ics_prefix);
13626 SendToICS("revert\n");
13632 switch (gameMode) {
13633 case MachinePlaysWhite:
13634 case MachinePlaysBlack:
13635 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13636 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13639 if (forwardMostMove < 2) return;
13640 currentMove = forwardMostMove = forwardMostMove - 2;
13641 whiteTimeRemaining = timeRemaining[0][currentMove];
13642 blackTimeRemaining = timeRemaining[1][currentMove];
13643 DisplayBothClocks();
13644 DisplayMove(currentMove - 1);
13645 ClearHighlights();/*!! could figure this out*/
13646 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13647 SendToProgram("remove\n", &first);
13648 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13651 case BeginningOfGame:
13655 case IcsPlayingWhite:
13656 case IcsPlayingBlack:
13657 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13658 SendToICS(ics_prefix);
13659 SendToICS("takeback 2\n");
13661 SendToICS(ics_prefix);
13662 SendToICS("takeback 1\n");
13671 ChessProgramState *cps;
13673 switch (gameMode) {
13674 case MachinePlaysWhite:
13675 if (!WhiteOnMove(forwardMostMove)) {
13676 DisplayError(_("It is your turn"), 0);
13681 case MachinePlaysBlack:
13682 if (WhiteOnMove(forwardMostMove)) {
13683 DisplayError(_("It is your turn"), 0);
13688 case TwoMachinesPlay:
13689 if (WhiteOnMove(forwardMostMove) ==
13690 (first.twoMachinesColor[0] == 'w')) {
13696 case BeginningOfGame:
13700 SendToProgram("?\n", cps);
13704 TruncateGameEvent()
13707 if (gameMode != EditGame) return;
13714 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13715 if (forwardMostMove > currentMove) {
13716 if (gameInfo.resultDetails != NULL) {
13717 free(gameInfo.resultDetails);
13718 gameInfo.resultDetails = NULL;
13719 gameInfo.result = GameUnfinished;
13721 forwardMostMove = currentMove;
13722 HistorySet(parseList, backwardMostMove, forwardMostMove,
13730 if (appData.noChessProgram) return;
13731 switch (gameMode) {
13732 case MachinePlaysWhite:
13733 if (WhiteOnMove(forwardMostMove)) {
13734 DisplayError(_("Wait until your turn"), 0);
13738 case BeginningOfGame:
13739 case MachinePlaysBlack:
13740 if (!WhiteOnMove(forwardMostMove)) {
13741 DisplayError(_("Wait until your turn"), 0);
13746 DisplayError(_("No hint available"), 0);
13749 SendToProgram("hint\n", &first);
13750 hintRequested = TRUE;
13756 if (appData.noChessProgram) return;
13757 switch (gameMode) {
13758 case MachinePlaysWhite:
13759 if (WhiteOnMove(forwardMostMove)) {
13760 DisplayError(_("Wait until your turn"), 0);
13764 case BeginningOfGame:
13765 case MachinePlaysBlack:
13766 if (!WhiteOnMove(forwardMostMove)) {
13767 DisplayError(_("Wait until your turn"), 0);
13772 EditPositionDone(TRUE);
13774 case TwoMachinesPlay:
13779 SendToProgram("bk\n", &first);
13780 bookOutput[0] = NULLCHAR;
13781 bookRequested = TRUE;
13787 char *tags = PGNTags(&gameInfo);
13788 TagsPopUp(tags, CmailMsg());
13792 /* end button procedures */
13795 PrintPosition(fp, move)
13801 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13802 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13803 char c = PieceToChar(boards[move][i][j]);
13804 fputc(c == 'x' ? '.' : c, fp);
13805 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13808 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13809 fprintf(fp, "white to play\n");
13811 fprintf(fp, "black to play\n");
13818 if (gameInfo.white != NULL) {
13819 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13825 /* Find last component of program's own name, using some heuristics */
13827 TidyProgramName(prog, host, buf)
13828 char *prog, *host, buf[MSG_SIZ];
13831 int local = (strcmp(host, "localhost") == 0);
13832 while (!local && (p = strchr(prog, ';')) != NULL) {
13834 while (*p == ' ') p++;
13837 if (*prog == '"' || *prog == '\'') {
13838 q = strchr(prog + 1, *prog);
13840 q = strchr(prog, ' ');
13842 if (q == NULL) q = prog + strlen(prog);
13844 while (p >= prog && *p != '/' && *p != '\\') p--;
13846 if(p == prog && *p == '"') p++;
13847 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13848 memcpy(buf, p, q - p);
13849 buf[q - p] = NULLCHAR;
13857 TimeControlTagValue()
13860 if (!appData.clockMode) {
13861 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13862 } else if (movesPerSession > 0) {
13863 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13864 } else if (timeIncrement == 0) {
13865 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13867 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13869 return StrSave(buf);
13875 /* This routine is used only for certain modes */
13876 VariantClass v = gameInfo.variant;
13877 ChessMove r = GameUnfinished;
13880 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13881 r = gameInfo.result;
13882 p = gameInfo.resultDetails;
13883 gameInfo.resultDetails = NULL;
13885 ClearGameInfo(&gameInfo);
13886 gameInfo.variant = v;
13888 switch (gameMode) {
13889 case MachinePlaysWhite:
13890 gameInfo.event = StrSave( appData.pgnEventHeader );
13891 gameInfo.site = StrSave(HostName());
13892 gameInfo.date = PGNDate();
13893 gameInfo.round = StrSave("-");
13894 gameInfo.white = StrSave(first.tidy);
13895 gameInfo.black = StrSave(UserName());
13896 gameInfo.timeControl = TimeControlTagValue();
13899 case MachinePlaysBlack:
13900 gameInfo.event = StrSave( appData.pgnEventHeader );
13901 gameInfo.site = StrSave(HostName());
13902 gameInfo.date = PGNDate();
13903 gameInfo.round = StrSave("-");
13904 gameInfo.white = StrSave(UserName());
13905 gameInfo.black = StrSave(first.tidy);
13906 gameInfo.timeControl = TimeControlTagValue();
13909 case TwoMachinesPlay:
13910 gameInfo.event = StrSave( appData.pgnEventHeader );
13911 gameInfo.site = StrSave(HostName());
13912 gameInfo.date = PGNDate();
13915 snprintf(buf, MSG_SIZ, "%d", roundNr);
13916 gameInfo.round = StrSave(buf);
13918 gameInfo.round = StrSave("-");
13920 if (first.twoMachinesColor[0] == 'w') {
13921 gameInfo.white = StrSave(first.tidy);
13922 gameInfo.black = StrSave(second.tidy);
13924 gameInfo.white = StrSave(second.tidy);
13925 gameInfo.black = StrSave(first.tidy);
13927 gameInfo.timeControl = TimeControlTagValue();
13931 gameInfo.event = StrSave("Edited game");
13932 gameInfo.site = StrSave(HostName());
13933 gameInfo.date = PGNDate();
13934 gameInfo.round = StrSave("-");
13935 gameInfo.white = StrSave("-");
13936 gameInfo.black = StrSave("-");
13937 gameInfo.result = r;
13938 gameInfo.resultDetails = p;
13942 gameInfo.event = StrSave("Edited position");
13943 gameInfo.site = StrSave(HostName());
13944 gameInfo.date = PGNDate();
13945 gameInfo.round = StrSave("-");
13946 gameInfo.white = StrSave("-");
13947 gameInfo.black = StrSave("-");
13950 case IcsPlayingWhite:
13951 case IcsPlayingBlack:
13956 case PlayFromGameFile:
13957 gameInfo.event = StrSave("Game from non-PGN file");
13958 gameInfo.site = StrSave(HostName());
13959 gameInfo.date = PGNDate();
13960 gameInfo.round = StrSave("-");
13961 gameInfo.white = StrSave("?");
13962 gameInfo.black = StrSave("?");
13971 ReplaceComment(index, text)
13979 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13980 pvInfoList[index-1].depth == len &&
13981 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13982 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13983 while (*text == '\n') text++;
13984 len = strlen(text);
13985 while (len > 0 && text[len - 1] == '\n') len--;
13987 if (commentList[index] != NULL)
13988 free(commentList[index]);
13991 commentList[index] = NULL;
13994 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13995 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13996 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13997 commentList[index] = (char *) malloc(len + 2);
13998 strncpy(commentList[index], text, len);
13999 commentList[index][len] = '\n';
14000 commentList[index][len + 1] = NULLCHAR;
14002 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14004 commentList[index] = (char *) malloc(len + 7);
14005 safeStrCpy(commentList[index], "{\n", 3);
14006 safeStrCpy(commentList[index]+2, text, len+1);
14007 commentList[index][len+2] = NULLCHAR;
14008 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14009 strcat(commentList[index], "\n}\n");
14023 if (ch == '\r') continue;
14025 } while (ch != '\0');
14029 AppendComment(index, text, addBraces)
14032 Boolean addBraces; // [HGM] braces: tells if we should add {}
14037 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14038 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14041 while (*text == '\n') text++;
14042 len = strlen(text);
14043 while (len > 0 && text[len - 1] == '\n') len--;
14045 if (len == 0) return;
14047 if (commentList[index] != NULL) {
14048 old = commentList[index];
14049 oldlen = strlen(old);
14050 while(commentList[index][oldlen-1] == '\n')
14051 commentList[index][--oldlen] = NULLCHAR;
14052 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14053 safeStrCpy(commentList[index], old, oldlen + len + 6);
14055 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14056 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14057 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14058 while (*text == '\n') { text++; len--; }
14059 commentList[index][--oldlen] = NULLCHAR;
14061 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14062 else strcat(commentList[index], "\n");
14063 strcat(commentList[index], text);
14064 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14065 else strcat(commentList[index], "\n");
14067 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14069 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14070 else commentList[index][0] = NULLCHAR;
14071 strcat(commentList[index], text);
14072 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14073 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14077 static char * FindStr( char * text, char * sub_text )
14079 char * result = strstr( text, sub_text );
14081 if( result != NULL ) {
14082 result += strlen( sub_text );
14088 /* [AS] Try to extract PV info from PGN comment */
14089 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14090 char *GetInfoFromComment( int index, char * text )
14092 char * sep = text, *p;
14094 if( text != NULL && index > 0 ) {
14097 int time = -1, sec = 0, deci;
14098 char * s_eval = FindStr( text, "[%eval " );
14099 char * s_emt = FindStr( text, "[%emt " );
14101 if( s_eval != NULL || s_emt != NULL ) {
14105 if( s_eval != NULL ) {
14106 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14110 if( delim != ']' ) {
14115 if( s_emt != NULL ) {
14120 /* We expect something like: [+|-]nnn.nn/dd */
14123 if(*text != '{') return text; // [HGM] braces: must be normal comment
14125 sep = strchr( text, '/' );
14126 if( sep == NULL || sep < (text+4) ) {
14131 if(p[1] == '(') { // comment starts with PV
14132 p = strchr(p, ')'); // locate end of PV
14133 if(p == NULL || sep < p+5) return text;
14134 // at this point we have something like "{(.*) +0.23/6 ..."
14135 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14136 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14137 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14139 time = -1; sec = -1; deci = -1;
14140 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14141 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14142 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14143 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14147 if( score_lo < 0 || score_lo >= 100 ) {
14151 if(sec >= 0) time = 600*time + 10*sec; else
14152 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14154 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14156 /* [HGM] PV time: now locate end of PV info */
14157 while( *++sep >= '0' && *sep <= '9'); // strip depth
14159 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14161 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14163 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14164 while(*sep == ' ') sep++;
14175 pvInfoList[index-1].depth = depth;
14176 pvInfoList[index-1].score = score;
14177 pvInfoList[index-1].time = 10*time; // centi-sec
14178 if(*sep == '}') *sep = 0; else *--sep = '{';
14179 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14185 SendToProgram(message, cps)
14187 ChessProgramState *cps;
14189 int count, outCount, error;
14192 if (cps->pr == NULL) return;
14195 if (appData.debugMode) {
14198 fprintf(debugFP, "%ld >%-6s: %s",
14199 SubtractTimeMarks(&now, &programStartTime),
14200 cps->which, message);
14203 count = strlen(message);
14204 outCount = OutputToProcess(cps->pr, message, count, &error);
14205 if (outCount < count && !exiting
14206 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14207 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14208 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14209 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14210 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14211 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14212 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14213 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14215 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14216 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14217 gameInfo.result = res;
14219 gameInfo.resultDetails = StrSave(buf);
14221 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14222 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14227 ReceiveFromProgram(isr, closure, message, count, error)
14228 InputSourceRef isr;
14236 ChessProgramState *cps = (ChessProgramState *)closure;
14238 if (isr != cps->isr) return; /* Killed intentionally */
14241 RemoveInputSource(cps->isr);
14242 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14243 _(cps->which), cps->program);
14244 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14245 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14246 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14247 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14248 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14250 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14251 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14252 gameInfo.result = res;
14254 gameInfo.resultDetails = StrSave(buf);
14256 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14257 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14259 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14260 _(cps->which), cps->program);
14261 RemoveInputSource(cps->isr);
14263 /* [AS] Program is misbehaving badly... kill it */
14264 if( count == -2 ) {
14265 DestroyChildProcess( cps->pr, 9 );
14269 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14274 if ((end_str = strchr(message, '\r')) != NULL)
14275 *end_str = NULLCHAR;
14276 if ((end_str = strchr(message, '\n')) != NULL)
14277 *end_str = NULLCHAR;
14279 if (appData.debugMode) {
14280 TimeMark now; int print = 1;
14281 char *quote = ""; char c; int i;
14283 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14284 char start = message[0];
14285 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14286 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14287 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14288 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14289 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14290 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14291 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14292 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14293 sscanf(message, "hint: %c", &c)!=1 &&
14294 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14295 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14296 print = (appData.engineComments >= 2);
14298 message[0] = start; // restore original message
14302 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14303 SubtractTimeMarks(&now, &programStartTime), cps->which,
14309 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14310 if (appData.icsEngineAnalyze) {
14311 if (strstr(message, "whisper") != NULL ||
14312 strstr(message, "kibitz") != NULL ||
14313 strstr(message, "tellics") != NULL) return;
14316 HandleMachineMove(message, cps);
14321 SendTimeControl(cps, mps, tc, inc, sd, st)
14322 ChessProgramState *cps;
14323 int mps, inc, sd, st;
14329 if( timeControl_2 > 0 ) {
14330 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14331 tc = timeControl_2;
14334 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14335 inc /= cps->timeOdds;
14336 st /= cps->timeOdds;
14338 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14341 /* Set exact time per move, normally using st command */
14342 if (cps->stKludge) {
14343 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14345 if (seconds == 0) {
14346 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14348 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14351 snprintf(buf, MSG_SIZ, "st %d\n", st);
14354 /* Set conventional or incremental time control, using level command */
14355 if (seconds == 0) {
14356 /* Note old gnuchess bug -- minutes:seconds used to not work.
14357 Fixed in later versions, but still avoid :seconds
14358 when seconds is 0. */
14359 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14361 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14362 seconds, inc/1000.);
14365 SendToProgram(buf, cps);
14367 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14368 /* Orthogonally, limit search to given depth */
14370 if (cps->sdKludge) {
14371 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14373 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14375 SendToProgram(buf, cps);
14378 if(cps->nps >= 0) { /* [HGM] nps */
14379 if(cps->supportsNPS == FALSE)
14380 cps->nps = -1; // don't use if engine explicitly says not supported!
14382 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14383 SendToProgram(buf, cps);
14388 ChessProgramState *WhitePlayer()
14389 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14391 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14392 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14398 SendTimeRemaining(cps, machineWhite)
14399 ChessProgramState *cps;
14400 int /*boolean*/ machineWhite;
14402 char message[MSG_SIZ];
14405 /* Note: this routine must be called when the clocks are stopped
14406 or when they have *just* been set or switched; otherwise
14407 it will be off by the time since the current tick started.
14409 if (machineWhite) {
14410 time = whiteTimeRemaining / 10;
14411 otime = blackTimeRemaining / 10;
14413 time = blackTimeRemaining / 10;
14414 otime = whiteTimeRemaining / 10;
14416 /* [HGM] translate opponent's time by time-odds factor */
14417 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14418 if (appData.debugMode) {
14419 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14422 if (time <= 0) time = 1;
14423 if (otime <= 0) otime = 1;
14425 snprintf(message, MSG_SIZ, "time %ld\n", time);
14426 SendToProgram(message, cps);
14428 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14429 SendToProgram(message, cps);
14433 BoolFeature(p, name, loc, cps)
14437 ChessProgramState *cps;
14440 int len = strlen(name);
14443 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14445 sscanf(*p, "%d", &val);
14447 while (**p && **p != ' ')
14449 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14450 SendToProgram(buf, cps);
14457 IntFeature(p, name, loc, cps)
14461 ChessProgramState *cps;
14464 int len = strlen(name);
14465 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14467 sscanf(*p, "%d", loc);
14468 while (**p && **p != ' ') (*p)++;
14469 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14470 SendToProgram(buf, cps);
14477 StringFeature(p, name, loc, cps)
14481 ChessProgramState *cps;
14484 int len = strlen(name);
14485 if (strncmp((*p), name, len) == 0
14486 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14488 sscanf(*p, "%[^\"]", loc);
14489 while (**p && **p != '\"') (*p)++;
14490 if (**p == '\"') (*p)++;
14491 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14492 SendToProgram(buf, cps);
14499 ParseOption(Option *opt, ChessProgramState *cps)
14500 // [HGM] options: process the string that defines an engine option, and determine
14501 // name, type, default value, and allowed value range
14503 char *p, *q, buf[MSG_SIZ];
14504 int n, min = (-1)<<31, max = 1<<31, def;
14506 if(p = strstr(opt->name, " -spin ")) {
14507 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14508 if(max < min) max = min; // enforce consistency
14509 if(def < min) def = min;
14510 if(def > max) def = max;
14515 } else if((p = strstr(opt->name, " -slider "))) {
14516 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14517 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14518 if(max < min) max = min; // enforce consistency
14519 if(def < min) def = min;
14520 if(def > max) def = max;
14524 opt->type = Spin; // Slider;
14525 } else if((p = strstr(opt->name, " -string "))) {
14526 opt->textValue = p+9;
14527 opt->type = TextBox;
14528 } else if((p = strstr(opt->name, " -file "))) {
14529 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14530 opt->textValue = p+7;
14531 opt->type = FileName; // FileName;
14532 } else if((p = strstr(opt->name, " -path "))) {
14533 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14534 opt->textValue = p+7;
14535 opt->type = PathName; // PathName;
14536 } else if(p = strstr(opt->name, " -check ")) {
14537 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14538 opt->value = (def != 0);
14539 opt->type = CheckBox;
14540 } else if(p = strstr(opt->name, " -combo ")) {
14541 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14542 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14543 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14544 opt->value = n = 0;
14545 while(q = StrStr(q, " /// ")) {
14546 n++; *q = 0; // count choices, and null-terminate each of them
14548 if(*q == '*') { // remember default, which is marked with * prefix
14552 cps->comboList[cps->comboCnt++] = q;
14554 cps->comboList[cps->comboCnt++] = NULL;
14556 opt->type = ComboBox;
14557 } else if(p = strstr(opt->name, " -button")) {
14558 opt->type = Button;
14559 } else if(p = strstr(opt->name, " -save")) {
14560 opt->type = SaveButton;
14561 } else return FALSE;
14562 *p = 0; // terminate option name
14563 // now look if the command-line options define a setting for this engine option.
14564 if(cps->optionSettings && cps->optionSettings[0])
14565 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14566 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14567 snprintf(buf, MSG_SIZ, "option %s", p);
14568 if(p = strstr(buf, ",")) *p = 0;
14569 if(q = strchr(buf, '=')) switch(opt->type) {
14571 for(n=0; n<opt->max; n++)
14572 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14575 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14579 opt->value = atoi(q+1);
14584 SendToProgram(buf, cps);
14590 FeatureDone(cps, val)
14591 ChessProgramState* cps;
14594 DelayedEventCallback cb = GetDelayedEvent();
14595 if ((cb == InitBackEnd3 && cps == &first) ||
14596 (cb == SettingsMenuIfReady && cps == &second) ||
14597 (cb == LoadEngine) ||
14598 (cb == TwoMachinesEventIfReady)) {
14599 CancelDelayedEvent();
14600 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14602 cps->initDone = val;
14605 /* Parse feature command from engine */
14607 ParseFeatures(args, cps)
14609 ChessProgramState *cps;
14617 while (*p == ' ') p++;
14618 if (*p == NULLCHAR) return;
14620 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14621 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14622 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14623 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14624 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14625 if (BoolFeature(&p, "reuse", &val, cps)) {
14626 /* Engine can disable reuse, but can't enable it if user said no */
14627 if (!val) cps->reuse = FALSE;
14630 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14631 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14632 if (gameMode == TwoMachinesPlay) {
14633 DisplayTwoMachinesTitle();
14639 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14640 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14641 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14642 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14643 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14644 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14645 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14646 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14647 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14648 if (IntFeature(&p, "done", &val, cps)) {
14649 FeatureDone(cps, val);
14652 /* Added by Tord: */
14653 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14654 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14655 /* End of additions by Tord */
14657 /* [HGM] added features: */
14658 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14659 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14660 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14661 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14662 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14663 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14664 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14665 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14666 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14667 SendToProgram(buf, cps);
14670 if(cps->nrOptions >= MAX_OPTIONS) {
14672 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14673 DisplayError(buf, 0);
14677 /* End of additions by HGM */
14679 /* unknown feature: complain and skip */
14681 while (*q && *q != '=') q++;
14682 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14683 SendToProgram(buf, cps);
14689 while (*p && *p != '\"') p++;
14690 if (*p == '\"') p++;
14692 while (*p && *p != ' ') p++;
14700 PeriodicUpdatesEvent(newState)
14703 if (newState == appData.periodicUpdates)
14706 appData.periodicUpdates=newState;
14708 /* Display type changes, so update it now */
14709 // DisplayAnalysis();
14711 /* Get the ball rolling again... */
14713 AnalysisPeriodicEvent(1);
14714 StartAnalysisClock();
14719 PonderNextMoveEvent(newState)
14722 if (newState == appData.ponderNextMove) return;
14723 if (gameMode == EditPosition) EditPositionDone(TRUE);
14725 SendToProgram("hard\n", &first);
14726 if (gameMode == TwoMachinesPlay) {
14727 SendToProgram("hard\n", &second);
14730 SendToProgram("easy\n", &first);
14731 thinkOutput[0] = NULLCHAR;
14732 if (gameMode == TwoMachinesPlay) {
14733 SendToProgram("easy\n", &second);
14736 appData.ponderNextMove = newState;
14740 NewSettingEvent(option, feature, command, value)
14742 int option, value, *feature;
14746 if (gameMode == EditPosition) EditPositionDone(TRUE);
14747 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14748 if(feature == NULL || *feature) SendToProgram(buf, &first);
14749 if (gameMode == TwoMachinesPlay) {
14750 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14755 ShowThinkingEvent()
14756 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14758 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14759 int newState = appData.showThinking
14760 // [HGM] thinking: other features now need thinking output as well
14761 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14763 if (oldState == newState) return;
14764 oldState = newState;
14765 if (gameMode == EditPosition) EditPositionDone(TRUE);
14767 SendToProgram("post\n", &first);
14768 if (gameMode == TwoMachinesPlay) {
14769 SendToProgram("post\n", &second);
14772 SendToProgram("nopost\n", &first);
14773 thinkOutput[0] = NULLCHAR;
14774 if (gameMode == TwoMachinesPlay) {
14775 SendToProgram("nopost\n", &second);
14778 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14782 AskQuestionEvent(title, question, replyPrefix, which)
14783 char *title; char *question; char *replyPrefix; char *which;
14785 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14786 if (pr == NoProc) return;
14787 AskQuestion(title, question, replyPrefix, pr);
14791 TypeInEvent(char firstChar)
14793 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14794 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14795 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14796 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14797 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14798 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14799 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14800 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14801 gameMode == Training) PopUpMoveDialog(firstChar);
14805 TypeInDoneEvent(char *move)
14808 int n, fromX, fromY, toX, toY;
14810 ChessMove moveType;
\r
14813 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14814 EditPositionPasteFEN(move);
\r
14817 // [HGM] movenum: allow move number to be typed in any mode
\r
14818 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14819 ToNrEvent(2*n-1);
\r
14823 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14824 gameMode != Training) {
\r
14825 DisplayMoveError(_("Displayed move is not current"));
\r
14827 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14828 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14829 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14830 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14831 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14832 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14834 DisplayMoveError(_("Could not parse move"));
\r
14840 DisplayMove(moveNumber)
14843 char message[MSG_SIZ];
14845 char cpThinkOutput[MSG_SIZ];
14847 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14849 if (moveNumber == forwardMostMove - 1 ||
14850 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14852 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14854 if (strchr(cpThinkOutput, '\n')) {
14855 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14858 *cpThinkOutput = NULLCHAR;
14861 /* [AS] Hide thinking from human user */
14862 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14863 *cpThinkOutput = NULLCHAR;
14864 if( thinkOutput[0] != NULLCHAR ) {
14867 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14868 cpThinkOutput[i] = '.';
14870 cpThinkOutput[i] = NULLCHAR;
14871 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14875 if (moveNumber == forwardMostMove - 1 &&
14876 gameInfo.resultDetails != NULL) {
14877 if (gameInfo.resultDetails[0] == NULLCHAR) {
14878 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14880 snprintf(res, MSG_SIZ, " {%s} %s",
14881 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14887 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14888 DisplayMessage(res, cpThinkOutput);
14890 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14891 WhiteOnMove(moveNumber) ? " " : ".. ",
14892 parseList[moveNumber], res);
14893 DisplayMessage(message, cpThinkOutput);
14898 DisplayComment(moveNumber, text)
14902 char title[MSG_SIZ];
14903 char buf[8000]; // comment can be long!
14906 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14907 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14909 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14910 WhiteOnMove(moveNumber) ? " " : ".. ",
14911 parseList[moveNumber]);
14913 // [HGM] PV info: display PV info together with (or as) comment
14914 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14915 if(text == NULL) text = "";
14916 score = pvInfoList[moveNumber].score;
14917 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14918 depth, (pvInfoList[moveNumber].time+50)/100, text);
14921 if (text != NULL && (appData.autoDisplayComment || commentUp))
14922 CommentPopUp(title, text);
14925 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14926 * might be busy thinking or pondering. It can be omitted if your
14927 * gnuchess is configured to stop thinking immediately on any user
14928 * input. However, that gnuchess feature depends on the FIONREAD
14929 * ioctl, which does not work properly on some flavors of Unix.
14933 ChessProgramState *cps;
14936 if (!cps->useSigint) return;
14937 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14938 switch (gameMode) {
14939 case MachinePlaysWhite:
14940 case MachinePlaysBlack:
14941 case TwoMachinesPlay:
14942 case IcsPlayingWhite:
14943 case IcsPlayingBlack:
14946 /* Skip if we know it isn't thinking */
14947 if (!cps->maybeThinking) return;
14948 if (appData.debugMode)
14949 fprintf(debugFP, "Interrupting %s\n", cps->which);
14950 InterruptChildProcess(cps->pr);
14951 cps->maybeThinking = FALSE;
14956 #endif /*ATTENTION*/
14962 if (whiteTimeRemaining <= 0) {
14965 if (appData.icsActive) {
14966 if (appData.autoCallFlag &&
14967 gameMode == IcsPlayingBlack && !blackFlag) {
14968 SendToICS(ics_prefix);
14969 SendToICS("flag\n");
14973 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14975 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14976 if (appData.autoCallFlag) {
14977 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14984 if (blackTimeRemaining <= 0) {
14987 if (appData.icsActive) {
14988 if (appData.autoCallFlag &&
14989 gameMode == IcsPlayingWhite && !whiteFlag) {
14990 SendToICS(ics_prefix);
14991 SendToICS("flag\n");
14995 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14997 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14998 if (appData.autoCallFlag) {
14999 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15012 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15013 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15016 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15018 if ( !WhiteOnMove(forwardMostMove) ) {
15019 /* White made time control */
15020 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15021 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15022 /* [HGM] time odds: correct new time quota for time odds! */
15023 / WhitePlayer()->timeOdds;
15024 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15026 lastBlack -= blackTimeRemaining;
15027 /* Black made time control */
15028 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15029 / WhitePlayer()->other->timeOdds;
15030 lastWhite = whiteTimeRemaining;
15035 DisplayBothClocks()
15037 int wom = gameMode == EditPosition ?
15038 !blackPlaysFirst : WhiteOnMove(currentMove);
15039 DisplayWhiteClock(whiteTimeRemaining, wom);
15040 DisplayBlackClock(blackTimeRemaining, !wom);
15044 /* Timekeeping seems to be a portability nightmare. I think everyone
15045 has ftime(), but I'm really not sure, so I'm including some ifdefs
15046 to use other calls if you don't. Clocks will be less accurate if
15047 you have neither ftime nor gettimeofday.
15050 /* VS 2008 requires the #include outside of the function */
15051 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15052 #include <sys/timeb.h>
15055 /* Get the current time as a TimeMark */
15060 #if HAVE_GETTIMEOFDAY
15062 struct timeval timeVal;
15063 struct timezone timeZone;
15065 gettimeofday(&timeVal, &timeZone);
15066 tm->sec = (long) timeVal.tv_sec;
15067 tm->ms = (int) (timeVal.tv_usec / 1000L);
15069 #else /*!HAVE_GETTIMEOFDAY*/
15072 // include <sys/timeb.h> / moved to just above start of function
15073 struct timeb timeB;
15076 tm->sec = (long) timeB.time;
15077 tm->ms = (int) timeB.millitm;
15079 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15080 tm->sec = (long) time(NULL);
15086 /* Return the difference in milliseconds between two
15087 time marks. We assume the difference will fit in a long!
15090 SubtractTimeMarks(tm2, tm1)
15091 TimeMark *tm2, *tm1;
15093 return 1000L*(tm2->sec - tm1->sec) +
15094 (long) (tm2->ms - tm1->ms);
15099 * Code to manage the game clocks.
15101 * In tournament play, black starts the clock and then white makes a move.
15102 * We give the human user a slight advantage if he is playing white---the
15103 * clocks don't run until he makes his first move, so it takes zero time.
15104 * Also, we don't account for network lag, so we could get out of sync
15105 * with GNU Chess's clock -- but then, referees are always right.
15108 static TimeMark tickStartTM;
15109 static long intendedTickLength;
15112 NextTickLength(timeRemaining)
15113 long timeRemaining;
15115 long nominalTickLength, nextTickLength;
15117 if (timeRemaining > 0L && timeRemaining <= 10000L)
15118 nominalTickLength = 100L;
15120 nominalTickLength = 1000L;
15121 nextTickLength = timeRemaining % nominalTickLength;
15122 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15124 return nextTickLength;
15127 /* Adjust clock one minute up or down */
15129 AdjustClock(Boolean which, int dir)
15131 if(which) blackTimeRemaining += 60000*dir;
15132 else whiteTimeRemaining += 60000*dir;
15133 DisplayBothClocks();
15136 /* Stop clocks and reset to a fresh time control */
15140 (void) StopClockTimer();
15141 if (appData.icsActive) {
15142 whiteTimeRemaining = blackTimeRemaining = 0;
15143 } else if (searchTime) {
15144 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15145 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15146 } else { /* [HGM] correct new time quote for time odds */
15147 whiteTC = blackTC = fullTimeControlString;
15148 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15149 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15151 if (whiteFlag || blackFlag) {
15153 whiteFlag = blackFlag = FALSE;
15155 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15156 DisplayBothClocks();
15159 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15161 /* Decrement running clock by amount of time that has passed */
15165 long timeRemaining;
15166 long lastTickLength, fudge;
15169 if (!appData.clockMode) return;
15170 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15174 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15176 /* Fudge if we woke up a little too soon */
15177 fudge = intendedTickLength - lastTickLength;
15178 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15180 if (WhiteOnMove(forwardMostMove)) {
15181 if(whiteNPS >= 0) lastTickLength = 0;
15182 timeRemaining = whiteTimeRemaining -= lastTickLength;
15183 if(timeRemaining < 0 && !appData.icsActive) {
15184 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15185 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15186 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15187 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15190 DisplayWhiteClock(whiteTimeRemaining - fudge,
15191 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15193 if(blackNPS >= 0) lastTickLength = 0;
15194 timeRemaining = blackTimeRemaining -= lastTickLength;
15195 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15196 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15198 blackStartMove = forwardMostMove;
15199 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15202 DisplayBlackClock(blackTimeRemaining - fudge,
15203 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15205 if (CheckFlags()) return;
15208 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15209 StartClockTimer(intendedTickLength);
15211 /* if the time remaining has fallen below the alarm threshold, sound the
15212 * alarm. if the alarm has sounded and (due to a takeback or time control
15213 * with increment) the time remaining has increased to a level above the
15214 * threshold, reset the alarm so it can sound again.
15217 if (appData.icsActive && appData.icsAlarm) {
15219 /* make sure we are dealing with the user's clock */
15220 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15221 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15224 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15225 alarmSounded = FALSE;
15226 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15228 alarmSounded = TRUE;
15234 /* A player has just moved, so stop the previously running
15235 clock and (if in clock mode) start the other one.
15236 We redisplay both clocks in case we're in ICS mode, because
15237 ICS gives us an update to both clocks after every move.
15238 Note that this routine is called *after* forwardMostMove
15239 is updated, so the last fractional tick must be subtracted
15240 from the color that is *not* on move now.
15243 SwitchClocks(int newMoveNr)
15245 long lastTickLength;
15247 int flagged = FALSE;
15251 if (StopClockTimer() && appData.clockMode) {
15252 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15253 if (!WhiteOnMove(forwardMostMove)) {
15254 if(blackNPS >= 0) lastTickLength = 0;
15255 blackTimeRemaining -= lastTickLength;
15256 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15257 // if(pvInfoList[forwardMostMove].time == -1)
15258 pvInfoList[forwardMostMove].time = // use GUI time
15259 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15261 if(whiteNPS >= 0) lastTickLength = 0;
15262 whiteTimeRemaining -= lastTickLength;
15263 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15264 // if(pvInfoList[forwardMostMove].time == -1)
15265 pvInfoList[forwardMostMove].time =
15266 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15268 flagged = CheckFlags();
15270 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15271 CheckTimeControl();
15273 if (flagged || !appData.clockMode) return;
15275 switch (gameMode) {
15276 case MachinePlaysBlack:
15277 case MachinePlaysWhite:
15278 case BeginningOfGame:
15279 if (pausing) return;
15283 case PlayFromGameFile:
15291 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15292 if(WhiteOnMove(forwardMostMove))
15293 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15294 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15298 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15299 whiteTimeRemaining : blackTimeRemaining);
15300 StartClockTimer(intendedTickLength);
15304 /* Stop both clocks */
15308 long lastTickLength;
15311 if (!StopClockTimer()) return;
15312 if (!appData.clockMode) return;
15316 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15317 if (WhiteOnMove(forwardMostMove)) {
15318 if(whiteNPS >= 0) lastTickLength = 0;
15319 whiteTimeRemaining -= lastTickLength;
15320 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15322 if(blackNPS >= 0) lastTickLength = 0;
15323 blackTimeRemaining -= lastTickLength;
15324 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15329 /* Start clock of player on move. Time may have been reset, so
15330 if clock is already running, stop and restart it. */
15334 (void) StopClockTimer(); /* in case it was running already */
15335 DisplayBothClocks();
15336 if (CheckFlags()) return;
15338 if (!appData.clockMode) return;
15339 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15341 GetTimeMark(&tickStartTM);
15342 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15343 whiteTimeRemaining : blackTimeRemaining);
15345 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15346 whiteNPS = blackNPS = -1;
15347 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15348 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15349 whiteNPS = first.nps;
15350 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15351 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15352 blackNPS = first.nps;
15353 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15354 whiteNPS = second.nps;
15355 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15356 blackNPS = second.nps;
15357 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15359 StartClockTimer(intendedTickLength);
15366 long second, minute, hour, day;
15368 static char buf[32];
15370 if (ms > 0 && ms <= 9900) {
15371 /* convert milliseconds to tenths, rounding up */
15372 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15374 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15378 /* convert milliseconds to seconds, rounding up */
15379 /* use floating point to avoid strangeness of integer division
15380 with negative dividends on many machines */
15381 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15388 day = second / (60 * 60 * 24);
15389 second = second % (60 * 60 * 24);
15390 hour = second / (60 * 60);
15391 second = second % (60 * 60);
15392 minute = second / 60;
15393 second = second % 60;
15396 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15397 sign, day, hour, minute, second);
15399 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15401 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15408 * This is necessary because some C libraries aren't ANSI C compliant yet.
15411 StrStr(string, match)
15412 char *string, *match;
15416 length = strlen(match);
15418 for (i = strlen(string) - length; i >= 0; i--, string++)
15419 if (!strncmp(match, string, length))
15426 StrCaseStr(string, match)
15427 char *string, *match;
15431 length = strlen(match);
15433 for (i = strlen(string) - length; i >= 0; i--, string++) {
15434 for (j = 0; j < length; j++) {
15435 if (ToLower(match[j]) != ToLower(string[j]))
15438 if (j == length) return string;
15452 c1 = ToLower(*s1++);
15453 c2 = ToLower(*s2++);
15454 if (c1 > c2) return 1;
15455 if (c1 < c2) return -1;
15456 if (c1 == NULLCHAR) return 0;
15465 return isupper(c) ? tolower(c) : c;
15473 return islower(c) ? toupper(c) : c;
15475 #endif /* !_amigados */
15483 if ((ret = (char *) malloc(strlen(s) + 1)))
15485 safeStrCpy(ret, s, strlen(s)+1);
15491 StrSavePtr(s, savePtr)
15492 char *s, **savePtr;
15497 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15498 safeStrCpy(*savePtr, s, strlen(s)+1);
15510 clock = time((time_t *)NULL);
15511 tm = localtime(&clock);
15512 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15513 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15514 return StrSave(buf);
15519 PositionToFEN(move, overrideCastling)
15521 char *overrideCastling;
15523 int i, j, fromX, fromY, toX, toY;
15530 whiteToPlay = (gameMode == EditPosition) ?
15531 !blackPlaysFirst : (move % 2 == 0);
15534 /* Piece placement data */
15535 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15537 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15538 if (boards[move][i][j] == EmptySquare) {
15540 } else { ChessSquare piece = boards[move][i][j];
15541 if (emptycount > 0) {
15542 if(emptycount<10) /* [HGM] can be >= 10 */
15543 *p++ = '0' + emptycount;
15544 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15547 if(PieceToChar(piece) == '+') {
15548 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15550 piece = (ChessSquare)(DEMOTED piece);
15552 *p++ = PieceToChar(piece);
15554 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15555 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15560 if (emptycount > 0) {
15561 if(emptycount<10) /* [HGM] can be >= 10 */
15562 *p++ = '0' + emptycount;
15563 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15570 /* [HGM] print Crazyhouse or Shogi holdings */
15571 if( gameInfo.holdingsWidth ) {
15572 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15574 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15575 piece = boards[move][i][BOARD_WIDTH-1];
15576 if( piece != EmptySquare )
15577 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15578 *p++ = PieceToChar(piece);
15580 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15581 piece = boards[move][BOARD_HEIGHT-i-1][0];
15582 if( piece != EmptySquare )
15583 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15584 *p++ = PieceToChar(piece);
15587 if( q == p ) *p++ = '-';
15593 *p++ = whiteToPlay ? 'w' : 'b';
15596 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15597 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15599 if(nrCastlingRights) {
15601 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15602 /* [HGM] write directly from rights */
15603 if(boards[move][CASTLING][2] != NoRights &&
15604 boards[move][CASTLING][0] != NoRights )
15605 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15606 if(boards[move][CASTLING][2] != NoRights &&
15607 boards[move][CASTLING][1] != NoRights )
15608 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15609 if(boards[move][CASTLING][5] != NoRights &&
15610 boards[move][CASTLING][3] != NoRights )
15611 *p++ = boards[move][CASTLING][3] + AAA;
15612 if(boards[move][CASTLING][5] != NoRights &&
15613 boards[move][CASTLING][4] != NoRights )
15614 *p++ = boards[move][CASTLING][4] + AAA;
15617 /* [HGM] write true castling rights */
15618 if( nrCastlingRights == 6 ) {
15619 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15620 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15621 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15622 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15623 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15624 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15625 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15626 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15629 if (q == p) *p++ = '-'; /* No castling rights */
15633 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15634 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15635 /* En passant target square */
15636 if (move > backwardMostMove) {
15637 fromX = moveList[move - 1][0] - AAA;
15638 fromY = moveList[move - 1][1] - ONE;
15639 toX = moveList[move - 1][2] - AAA;
15640 toY = moveList[move - 1][3] - ONE;
15641 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15642 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15643 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15645 /* 2-square pawn move just happened */
15647 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15651 } else if(move == backwardMostMove) {
15652 // [HGM] perhaps we should always do it like this, and forget the above?
15653 if((signed char)boards[move][EP_STATUS] >= 0) {
15654 *p++ = boards[move][EP_STATUS] + AAA;
15655 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15666 /* [HGM] find reversible plies */
15667 { int i = 0, j=move;
15669 if (appData.debugMode) { int k;
15670 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15671 for(k=backwardMostMove; k<=forwardMostMove; k++)
15672 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15676 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15677 if( j == backwardMostMove ) i += initialRulePlies;
15678 sprintf(p, "%d ", i);
15679 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15681 /* Fullmove number */
15682 sprintf(p, "%d", (move / 2) + 1);
15684 return StrSave(buf);
15688 ParseFEN(board, blackPlaysFirst, fen)
15690 int *blackPlaysFirst;
15700 /* [HGM] by default clear Crazyhouse holdings, if present */
15701 if(gameInfo.holdingsWidth) {
15702 for(i=0; i<BOARD_HEIGHT; i++) {
15703 board[i][0] = EmptySquare; /* black holdings */
15704 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15705 board[i][1] = (ChessSquare) 0; /* black counts */
15706 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15710 /* Piece placement data */
15711 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15714 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15715 if (*p == '/') p++;
15716 emptycount = gameInfo.boardWidth - j;
15717 while (emptycount--)
15718 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15720 #if(BOARD_FILES >= 10)
15721 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15722 p++; emptycount=10;
15723 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15724 while (emptycount--)
15725 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15727 } else if (isdigit(*p)) {
15728 emptycount = *p++ - '0';
15729 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15730 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15731 while (emptycount--)
15732 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15733 } else if (*p == '+' || isalpha(*p)) {
15734 if (j >= gameInfo.boardWidth) return FALSE;
15736 piece = CharToPiece(*++p);
15737 if(piece == EmptySquare) return FALSE; /* unknown piece */
15738 piece = (ChessSquare) (PROMOTED piece ); p++;
15739 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15740 } else piece = CharToPiece(*p++);
15742 if(piece==EmptySquare) return FALSE; /* unknown piece */
15743 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15744 piece = (ChessSquare) (PROMOTED piece);
15745 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15748 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15754 while (*p == '/' || *p == ' ') p++;
15756 /* [HGM] look for Crazyhouse holdings here */
15757 while(*p==' ') p++;
15758 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15760 if(*p == '-' ) p++; /* empty holdings */ else {
15761 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15762 /* if we would allow FEN reading to set board size, we would */
15763 /* have to add holdings and shift the board read so far here */
15764 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15766 if((int) piece >= (int) BlackPawn ) {
15767 i = (int)piece - (int)BlackPawn;
15768 i = PieceToNumber((ChessSquare)i);
15769 if( i >= gameInfo.holdingsSize ) return FALSE;
15770 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15771 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15773 i = (int)piece - (int)WhitePawn;
15774 i = PieceToNumber((ChessSquare)i);
15775 if( i >= gameInfo.holdingsSize ) return FALSE;
15776 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15777 board[i][BOARD_WIDTH-2]++; /* black holdings */
15784 while(*p == ' ') p++;
15788 if(appData.colorNickNames) {
15789 if( c == appData.colorNickNames[0] ) c = 'w'; else
15790 if( c == appData.colorNickNames[1] ) c = 'b';
15794 *blackPlaysFirst = FALSE;
15797 *blackPlaysFirst = TRUE;
15803 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15804 /* return the extra info in global variiables */
15806 /* set defaults in case FEN is incomplete */
15807 board[EP_STATUS] = EP_UNKNOWN;
15808 for(i=0; i<nrCastlingRights; i++ ) {
15809 board[CASTLING][i] =
15810 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15811 } /* assume possible unless obviously impossible */
15812 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15813 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15814 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15815 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15816 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15817 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15818 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15819 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15822 while(*p==' ') p++;
15823 if(nrCastlingRights) {
15824 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15825 /* castling indicator present, so default becomes no castlings */
15826 for(i=0; i<nrCastlingRights; i++ ) {
15827 board[CASTLING][i] = NoRights;
15830 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15831 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15832 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15833 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15834 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15836 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15837 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15838 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15840 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15841 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15842 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15843 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15844 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15845 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15848 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15849 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15850 board[CASTLING][2] = whiteKingFile;
15853 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15854 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15855 board[CASTLING][2] = whiteKingFile;
15858 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15859 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15860 board[CASTLING][5] = blackKingFile;
15863 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15864 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15865 board[CASTLING][5] = blackKingFile;
15868 default: /* FRC castlings */
15869 if(c >= 'a') { /* black rights */
15870 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15871 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15872 if(i == BOARD_RGHT) break;
15873 board[CASTLING][5] = i;
15875 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15876 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15878 board[CASTLING][3] = c;
15880 board[CASTLING][4] = c;
15881 } else { /* white rights */
15882 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15883 if(board[0][i] == WhiteKing) break;
15884 if(i == BOARD_RGHT) break;
15885 board[CASTLING][2] = i;
15886 c -= AAA - 'a' + 'A';
15887 if(board[0][c] >= WhiteKing) break;
15889 board[CASTLING][0] = c;
15891 board[CASTLING][1] = c;
15895 for(i=0; i<nrCastlingRights; i++)
15896 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15897 if (appData.debugMode) {
15898 fprintf(debugFP, "FEN castling rights:");
15899 for(i=0; i<nrCastlingRights; i++)
15900 fprintf(debugFP, " %d", board[CASTLING][i]);
15901 fprintf(debugFP, "\n");
15904 while(*p==' ') p++;
15907 /* read e.p. field in games that know e.p. capture */
15908 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15909 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15911 p++; board[EP_STATUS] = EP_NONE;
15913 char c = *p++ - AAA;
15915 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15916 if(*p >= '0' && *p <='9') p++;
15917 board[EP_STATUS] = c;
15922 if(sscanf(p, "%d", &i) == 1) {
15923 FENrulePlies = i; /* 50-move ply counter */
15924 /* (The move number is still ignored) */
15931 EditPositionPasteFEN(char *fen)
15934 Board initial_position;
15936 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15937 DisplayError(_("Bad FEN position in clipboard"), 0);
15940 int savedBlackPlaysFirst = blackPlaysFirst;
15941 EditPositionEvent();
15942 blackPlaysFirst = savedBlackPlaysFirst;
15943 CopyBoard(boards[0], initial_position);
15944 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15945 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15946 DisplayBothClocks();
15947 DrawPosition(FALSE, boards[currentMove]);
15952 static char cseq[12] = "\\ ";
15954 Boolean set_cont_sequence(char *new_seq)
15959 // handle bad attempts to set the sequence
15961 return 0; // acceptable error - no debug
15963 len = strlen(new_seq);
15964 ret = (len > 0) && (len < sizeof(cseq));
15966 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15967 else if (appData.debugMode)
15968 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15973 reformat a source message so words don't cross the width boundary. internal
15974 newlines are not removed. returns the wrapped size (no null character unless
15975 included in source message). If dest is NULL, only calculate the size required
15976 for the dest buffer. lp argument indicats line position upon entry, and it's
15977 passed back upon exit.
15979 int wrap(char *dest, char *src, int count, int width, int *lp)
15981 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15983 cseq_len = strlen(cseq);
15984 old_line = line = *lp;
15985 ansi = len = clen = 0;
15987 for (i=0; i < count; i++)
15989 if (src[i] == '\033')
15992 // if we hit the width, back up
15993 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15995 // store i & len in case the word is too long
15996 old_i = i, old_len = len;
15998 // find the end of the last word
15999 while (i && src[i] != ' ' && src[i] != '\n')
16005 // word too long? restore i & len before splitting it
16006 if ((old_i-i+clen) >= width)
16013 if (i && src[i-1] == ' ')
16016 if (src[i] != ' ' && src[i] != '\n')
16023 // now append the newline and continuation sequence
16028 strncpy(dest+len, cseq, cseq_len);
16036 dest[len] = src[i];
16040 if (src[i] == '\n')
16045 if (dest && appData.debugMode)
16047 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16048 count, width, line, len, *lp);
16049 show_bytes(debugFP, src, count);
16050 fprintf(debugFP, "\ndest: ");
16051 show_bytes(debugFP, dest, len);
16052 fprintf(debugFP, "\n");
16054 *lp = dest ? line : old_line;
16059 // [HGM] vari: routines for shelving variations
16062 PushTail(int firstMove, int lastMove)
16064 int i, j, nrMoves = lastMove - firstMove;
16066 if(appData.icsActive) { // only in local mode
16067 forwardMostMove = currentMove; // mimic old ICS behavior
16070 if(storedGames >= MAX_VARIATIONS-1) return;
16072 // push current tail of game on stack
16073 savedResult[storedGames] = gameInfo.result;
16074 savedDetails[storedGames] = gameInfo.resultDetails;
16075 gameInfo.resultDetails = NULL;
16076 savedFirst[storedGames] = firstMove;
16077 savedLast [storedGames] = lastMove;
16078 savedFramePtr[storedGames] = framePtr;
16079 framePtr -= nrMoves; // reserve space for the boards
16080 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16081 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16082 for(j=0; j<MOVE_LEN; j++)
16083 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16084 for(j=0; j<2*MOVE_LEN; j++)
16085 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16086 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16087 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16088 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16089 pvInfoList[firstMove+i-1].depth = 0;
16090 commentList[framePtr+i] = commentList[firstMove+i];
16091 commentList[firstMove+i] = NULL;
16095 forwardMostMove = firstMove; // truncate game so we can start variation
16096 if(storedGames == 1) GreyRevert(FALSE);
16100 PopTail(Boolean annotate)
16103 char buf[8000], moveBuf[20];
16105 if(appData.icsActive) return FALSE; // only in local mode
16106 if(!storedGames) return FALSE; // sanity
16107 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16110 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16111 nrMoves = savedLast[storedGames] - currentMove;
16114 if(!WhiteOnMove(currentMove))
16115 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16116 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16117 for(i=currentMove; i<forwardMostMove; i++) {
16119 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16120 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16121 strcat(buf, moveBuf);
16122 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16123 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16127 for(i=1; i<=nrMoves; i++) { // copy last variation back
16128 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16129 for(j=0; j<MOVE_LEN; j++)
16130 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16131 for(j=0; j<2*MOVE_LEN; j++)
16132 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16133 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16134 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16135 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16136 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16137 commentList[currentMove+i] = commentList[framePtr+i];
16138 commentList[framePtr+i] = NULL;
16140 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16141 framePtr = savedFramePtr[storedGames];
16142 gameInfo.result = savedResult[storedGames];
16143 if(gameInfo.resultDetails != NULL) {
16144 free(gameInfo.resultDetails);
16146 gameInfo.resultDetails = savedDetails[storedGames];
16147 forwardMostMove = currentMove + nrMoves;
16148 if(storedGames == 0) GreyRevert(TRUE);
16154 { // remove all shelved variations
16156 for(i=0; i<storedGames; i++) {
16157 if(savedDetails[i])
16158 free(savedDetails[i]);
16159 savedDetails[i] = NULL;
16161 for(i=framePtr; i<MAX_MOVES; i++) {
16162 if(commentList[i]) free(commentList[i]);
16163 commentList[i] = NULL;
16165 framePtr = MAX_MOVES-1;
16170 LoadVariation(int index, char *text)
16171 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16172 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16173 int level = 0, move;
16175 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16176 // first find outermost bracketing variation
16177 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16178 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16179 if(*p == '{') wait = '}'; else
16180 if(*p == '[') wait = ']'; else
16181 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16182 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16184 if(*p == wait) wait = NULLCHAR; // closing ]} found
16187 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16188 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16189 end[1] = NULLCHAR; // clip off comment beyond variation
16190 ToNrEvent(currentMove-1);
16191 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16192 // kludge: use ParsePV() to append variation to game
16193 move = currentMove;
16194 ParsePV(start, TRUE);
16195 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16196 ClearPremoveHighlights();
16198 ToNrEvent(currentMove+1);