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;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
864 void Load(ChessProgramState *cps, int i)
866 char *p, *q, buf[MSG_SIZ];
867 if(engineLine[0]) { // an engine was selected from the combo box
868 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
869 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
870 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
871 ParseArgsFromString(buf);
873 ReplaceEngine(cps, i);
877 while(q = strchr(p, SLASH)) p = q+1;
878 if(*p== NULLCHAR) return;
879 appData.chessProgram[i] = strdup(p);
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] = ".";
887 appData.isUCI[i] = isUCI;
888 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
889 appData.hasOwnBookUCI[i] = hasBook;
892 q = firstChessProgramNames;
893 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
894 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
895 v1 ? " -firstProtocolVersion 1" : "",
896 hasBook ? "" : " -fNoOwnBookUCI",
897 isUCI ? " -fUCI" : "",
898 storeVariant ? " -variant " : "",
899 storeVariant ? VariantName(gameInfo.variant) : "");
900 fprintf(debugFP, "new line: %s", buf);
901 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
902 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
905 ReplaceEngine(cps, i);
911 int matched, min, sec;
913 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
914 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
916 GetTimeMark(&programStartTime);
917 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
918 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
921 programStats.ok_to_send = 1;
922 programStats.seen_stat = 0;
925 * Initialize game list
931 * Internet chess server status
933 if (appData.icsActive) {
934 appData.matchMode = FALSE;
935 appData.matchGames = 0;
937 appData.noChessProgram = !appData.zippyPlay;
939 appData.zippyPlay = FALSE;
940 appData.zippyTalk = FALSE;
941 appData.noChessProgram = TRUE;
943 if (*appData.icsHelper != NULLCHAR) {
944 appData.useTelnet = TRUE;
945 appData.telnetProgram = appData.icsHelper;
948 appData.zippyTalk = appData.zippyPlay = FALSE;
951 /* [AS] Initialize pv info list [HGM] and game state */
955 for( i=0; i<=framePtr; i++ ) {
956 pvInfoList[i].depth = -1;
957 boards[i][EP_STATUS] = EP_NONE;
958 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
963 * Parse timeControl resource
965 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
966 appData.movesPerSession)) {
968 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
969 DisplayFatalError(buf, 0, 2);
973 * Parse searchTime resource
975 if (*appData.searchTime != NULLCHAR) {
976 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978 searchTime = min * 60;
979 } else if (matched == 2) {
980 searchTime = min * 60 + sec;
983 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
984 DisplayFatalError(buf, 0, 2);
988 /* [AS] Adjudication threshold */
989 adjudicateLossThreshold = appData.adjudicateLossThreshold;
991 InitEngine(&first, 0);
992 InitEngine(&second, 1);
995 if (appData.icsActive) {
996 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
997 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
998 appData.clockMode = FALSE;
999 first.sendTime = second.sendTime = 0;
1003 /* Override some settings from environment variables, for backward
1004 compatibility. Unfortunately it's not feasible to have the env
1005 vars just set defaults, at least in xboard. Ugh.
1007 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1012 if (!appData.icsActive) {
1016 /* Check for variants that are supported only in ICS mode,
1017 or not at all. Some that are accepted here nevertheless
1018 have bugs; see comments below.
1020 VariantClass variant = StringToVariant(appData.variant);
1022 case VariantBughouse: /* need four players and two boards */
1023 case VariantKriegspiel: /* need to hide pieces and move details */
1024 /* case VariantFischeRandom: (Fabien: moved below) */
1025 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1026 if( (len > MSG_SIZ) && appData.debugMode )
1027 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1029 DisplayFatalError(buf, 0, 2);
1032 case VariantUnknown:
1033 case VariantLoadable:
1043 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1044 if( (len > MSG_SIZ) && appData.debugMode )
1045 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1047 DisplayFatalError(buf, 0, 2);
1050 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1051 case VariantFairy: /* [HGM] TestLegality definitely off! */
1052 case VariantGothic: /* [HGM] should work */
1053 case VariantCapablanca: /* [HGM] should work */
1054 case VariantCourier: /* [HGM] initial forced moves not implemented */
1055 case VariantShogi: /* [HGM] could still mate with pawn drop */
1056 case VariantKnightmate: /* [HGM] should work */
1057 case VariantCylinder: /* [HGM] untested */
1058 case VariantFalcon: /* [HGM] untested */
1059 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1060 offboard interposition not understood */
1061 case VariantNormal: /* definitely works! */
1062 case VariantWildCastle: /* pieces not automatically shuffled */
1063 case VariantNoCastle: /* pieces not automatically shuffled */
1064 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1065 case VariantLosers: /* should work except for win condition,
1066 and doesn't know captures are mandatory */
1067 case VariantSuicide: /* should work except for win condition,
1068 and doesn't know captures are mandatory */
1069 case VariantGiveaway: /* should work except for win condition,
1070 and doesn't know captures are mandatory */
1071 case VariantTwoKings: /* should work */
1072 case VariantAtomic: /* should work except for win condition */
1073 case Variant3Check: /* should work except for win condition */
1074 case VariantShatranj: /* should work except for all win conditions */
1075 case VariantMakruk: /* should work except for daw countdown */
1076 case VariantBerolina: /* might work if TestLegality is off */
1077 case VariantCapaRandom: /* should work */
1078 case VariantJanus: /* should work */
1079 case VariantSuper: /* experimental */
1080 case VariantGreat: /* experimental, requires legality testing to be off */
1081 case VariantSChess: /* S-Chess, should work */
1082 case VariantSpartan: /* should work */
1089 int NextIntegerFromString( char ** str, long * value )
1094 while( *s == ' ' || *s == '\t' ) {
1100 if( *s >= '0' && *s <= '9' ) {
1101 while( *s >= '0' && *s <= '9' ) {
1102 *value = *value * 10 + (*s - '0');
1114 int NextTimeControlFromString( char ** str, long * value )
1117 int result = NextIntegerFromString( str, &temp );
1120 *value = temp * 60; /* Minutes */
1121 if( **str == ':' ) {
1123 result = NextIntegerFromString( str, &temp );
1124 *value += temp; /* Seconds */
1131 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1132 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1133 int result = -1, type = 0; long temp, temp2;
1135 if(**str != ':') return -1; // old params remain in force!
1137 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1138 if( NextIntegerFromString( str, &temp ) ) return -1;
1139 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1142 /* time only: incremental or sudden-death time control */
1143 if(**str == '+') { /* increment follows; read it */
1145 if(**str == '!') type = *(*str)++; // Bronstein TC
1146 if(result = NextIntegerFromString( str, &temp2)) return -1;
1147 *inc = temp2 * 1000;
1148 if(**str == '.') { // read fraction of increment
1149 char *start = ++(*str);
1150 if(result = NextIntegerFromString( str, &temp2)) return -1;
1152 while(start++ < *str) temp2 /= 10;
1156 *moves = 0; *tc = temp * 1000; *incType = type;
1160 (*str)++; /* classical time control */
1161 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1172 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1173 { /* [HGM] get time to add from the multi-session time-control string */
1174 int incType, moves=1; /* kludge to force reading of first session */
1175 long time, increment;
1178 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1179 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1181 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1182 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1183 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1184 if(movenr == -1) return time; /* last move before new session */
1185 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1186 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1187 if(!moves) return increment; /* current session is incremental */
1188 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1189 } while(movenr >= -1); /* try again for next session */
1191 return 0; // no new time quota on this move
1195 ParseTimeControl(tc, ti, mps)
1202 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1205 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1206 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1207 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1211 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1213 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1216 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1218 snprintf(buf, MSG_SIZ, ":%s", mytc);
1220 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1222 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1227 /* Parse second time control */
1230 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1238 timeControl_2 = tc2 * 1000;
1248 timeControl = tc1 * 1000;
1251 timeIncrement = ti * 1000; /* convert to ms */
1252 movesPerSession = 0;
1255 movesPerSession = mps;
1263 if (appData.debugMode) {
1264 fprintf(debugFP, "%s\n", programVersion);
1267 set_cont_sequence(appData.wrapContSeq);
1268 if (appData.matchGames > 0) {
1269 appData.matchMode = TRUE;
1270 } else if (appData.matchMode) {
1271 appData.matchGames = 1;
1273 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1274 appData.matchGames = appData.sameColorGames;
1275 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1276 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1277 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1280 if (appData.noChessProgram || first.protocolVersion == 1) {
1283 /* kludge: allow timeout for initial "feature" commands */
1285 DisplayMessage("", _("Starting chess program"));
1286 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1291 CalculateIndex(int index, int gameNr)
1292 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1294 if(index > 0) return index; // fixed nmber
1295 if(index == 0) return 1;
1296 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1297 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1302 LoadGameOrPosition(int gameNr)
1303 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1304 if (*appData.loadGameFile != NULLCHAR) {
1305 if (!LoadGameFromFile(appData.loadGameFile,
1306 CalculateIndex(appData.loadGameIndex, gameNr),
1307 appData.loadGameFile, FALSE)) {
1308 DisplayFatalError(_("Bad game file"), 0, 1);
1311 } else if (*appData.loadPositionFile != NULLCHAR) {
1312 if (!LoadPositionFromFile(appData.loadPositionFile,
1313 CalculateIndex(appData.loadPositionIndex, gameNr),
1314 appData.loadPositionFile)) {
1315 DisplayFatalError(_("Bad position file"), 0, 1);
1323 ReserveGame(int gameNr, char resChar)
1325 FILE *tf = fopen(appData.tourneyFile, "r+");
1326 char *p, *q, c, buf[MSG_SIZ];
1327 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1328 safeStrCpy(buf, lastMsg, MSG_SIZ);
1329 DisplayMessage(_("Pick new game"), "");
1330 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1331 ParseArgsFromFile(tf);
1332 p = q = appData.results;
1333 if(appData.debugMode) {
1334 char *r = appData.participants;
1335 fprintf(debugFP, "results = '%s'\n", p);
1336 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1337 fprintf(debugFP, "\n");
1339 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1341 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1342 safeStrCpy(q, p, strlen(p) + 2);
1343 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1344 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1345 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1346 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1349 fseek(tf, -(strlen(p)+4), SEEK_END);
1351 if(c != '"') // depending on DOS or Unix line endings we can be one off
1352 fseek(tf, -(strlen(p)+2), SEEK_END);
1353 else fseek(tf, -(strlen(p)+3), SEEK_END);
1354 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1355 DisplayMessage(buf, "");
1356 free(p); appData.results = q;
1357 if(nextGame <= appData.matchGames && resChar != ' ' &&
1358 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1359 UnloadEngine(&first); // next game belongs to other pairing;
1360 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1365 MatchEvent(int mode)
1366 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1368 /* Set up machine vs. machine match */
1370 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1371 if(appData.tourneyFile[0]) {
1373 if(nextGame > appData.matchGames) {
1375 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1376 DisplayError(buf, 0);
1377 appData.tourneyFile[0] = 0;
1381 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1382 DisplayFatalError(_("Can't have a match with no chess programs"),
1387 matchGame = roundNr = 1;
1388 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1393 InitBackEnd3 P((void))
1395 GameMode initialMode;
1399 InitChessProgram(&first, startedFromSetupPosition);
1401 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1402 free(programVersion);
1403 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1404 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1407 if (appData.icsActive) {
1409 /* [DM] Make a console window if needed [HGM] merged ifs */
1415 if (*appData.icsCommPort != NULLCHAR)
1416 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1417 appData.icsCommPort);
1419 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1420 appData.icsHost, appData.icsPort);
1422 if( (len > MSG_SIZ) && appData.debugMode )
1423 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1425 DisplayFatalError(buf, err, 1);
1430 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1432 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1433 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1434 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 } else if (appData.noChessProgram) {
1441 if (*appData.cmailGameName != NULLCHAR) {
1443 OpenLoopback(&cmailPR);
1445 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1449 DisplayMessage("", "");
1450 if (StrCaseCmp(appData.initialMode, "") == 0) {
1451 initialMode = BeginningOfGame;
1452 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1453 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1454 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1455 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1458 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1459 initialMode = TwoMachinesPlay;
1460 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1461 initialMode = AnalyzeFile;
1462 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1463 initialMode = AnalyzeMode;
1464 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1465 initialMode = MachinePlaysWhite;
1466 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1467 initialMode = MachinePlaysBlack;
1468 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1469 initialMode = EditGame;
1470 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1471 initialMode = EditPosition;
1472 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1473 initialMode = Training;
1475 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1476 if( (len > MSG_SIZ) && appData.debugMode )
1477 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1479 DisplayFatalError(buf, 0, 2);
1483 if (appData.matchMode) {
1484 if(appData.tourneyFile[0]) { // start tourney from command line
1486 if(f = fopen(appData.tourneyFile, "r")) {
1487 ParseArgsFromFile(f); // make sure tourney parmeters re known
1489 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1492 } else if (*appData.cmailGameName != NULLCHAR) {
1493 /* Set up cmail mode */
1494 ReloadCmailMsgEvent(TRUE);
1496 /* Set up other modes */
1497 if (initialMode == AnalyzeFile) {
1498 if (*appData.loadGameFile == NULLCHAR) {
1499 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1503 if (*appData.loadGameFile != NULLCHAR) {
1504 (void) LoadGameFromFile(appData.loadGameFile,
1505 appData.loadGameIndex,
1506 appData.loadGameFile, TRUE);
1507 } else if (*appData.loadPositionFile != NULLCHAR) {
1508 (void) LoadPositionFromFile(appData.loadPositionFile,
1509 appData.loadPositionIndex,
1510 appData.loadPositionFile);
1511 /* [HGM] try to make self-starting even after FEN load */
1512 /* to allow automatic setup of fairy variants with wtm */
1513 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1514 gameMode = BeginningOfGame;
1515 setboardSpoiledMachineBlack = 1;
1517 /* [HGM] loadPos: make that every new game uses the setup */
1518 /* from file as long as we do not switch variant */
1519 if(!blackPlaysFirst) {
1520 startedFromPositionFile = TRUE;
1521 CopyBoard(filePosition, boards[0]);
1524 if (initialMode == AnalyzeMode) {
1525 if (appData.noChessProgram) {
1526 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1529 if (appData.icsActive) {
1530 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1534 } else if (initialMode == AnalyzeFile) {
1535 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1536 ShowThinkingEvent();
1538 AnalysisPeriodicEvent(1);
1539 } else if (initialMode == MachinePlaysWhite) {
1540 if (appData.noChessProgram) {
1541 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1545 if (appData.icsActive) {
1546 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1550 MachineWhiteEvent();
1551 } else if (initialMode == MachinePlaysBlack) {
1552 if (appData.noChessProgram) {
1553 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1557 if (appData.icsActive) {
1558 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1562 MachineBlackEvent();
1563 } else if (initialMode == TwoMachinesPlay) {
1564 if (appData.noChessProgram) {
1565 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1569 if (appData.icsActive) {
1570 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1575 } else if (initialMode == EditGame) {
1577 } else if (initialMode == EditPosition) {
1578 EditPositionEvent();
1579 } else if (initialMode == Training) {
1580 if (*appData.loadGameFile == NULLCHAR) {
1581 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1590 * Establish will establish a contact to a remote host.port.
1591 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1592 * used to talk to the host.
1593 * Returns 0 if okay, error code if not.
1600 if (*appData.icsCommPort != NULLCHAR) {
1601 /* Talk to the host through a serial comm port */
1602 return OpenCommPort(appData.icsCommPort, &icsPR);
1604 } else if (*appData.gateway != NULLCHAR) {
1605 if (*appData.remoteShell == NULLCHAR) {
1606 /* Use the rcmd protocol to run telnet program on a gateway host */
1607 snprintf(buf, sizeof(buf), "%s %s %s",
1608 appData.telnetProgram, appData.icsHost, appData.icsPort);
1609 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1612 /* Use the rsh program to run telnet program on a gateway host */
1613 if (*appData.remoteUser == NULLCHAR) {
1614 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1615 appData.gateway, appData.telnetProgram,
1616 appData.icsHost, appData.icsPort);
1618 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1619 appData.remoteShell, appData.gateway,
1620 appData.remoteUser, appData.telnetProgram,
1621 appData.icsHost, appData.icsPort);
1623 return StartChildProcess(buf, "", &icsPR);
1626 } else if (appData.useTelnet) {
1627 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1630 /* TCP socket interface differs somewhat between
1631 Unix and NT; handle details in the front end.
1633 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1637 void EscapeExpand(char *p, char *q)
1638 { // [HGM] initstring: routine to shape up string arguments
1639 while(*p++ = *q++) if(p[-1] == '\\')
1641 case 'n': p[-1] = '\n'; break;
1642 case 'r': p[-1] = '\r'; break;
1643 case 't': p[-1] = '\t'; break;
1644 case '\\': p[-1] = '\\'; break;
1645 case 0: *p = 0; return;
1646 default: p[-1] = q[-1]; break;
1651 show_bytes(fp, buf, count)
1657 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1658 fprintf(fp, "\\%03o", *buf & 0xff);
1667 /* Returns an errno value */
1669 OutputMaybeTelnet(pr, message, count, outError)
1675 char buf[8192], *p, *q, *buflim;
1676 int left, newcount, outcount;
1678 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1679 *appData.gateway != NULLCHAR) {
1680 if (appData.debugMode) {
1681 fprintf(debugFP, ">ICS: ");
1682 show_bytes(debugFP, message, count);
1683 fprintf(debugFP, "\n");
1685 return OutputToProcess(pr, message, count, outError);
1688 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1695 if (appData.debugMode) {
1696 fprintf(debugFP, ">ICS: ");
1697 show_bytes(debugFP, buf, newcount);
1698 fprintf(debugFP, "\n");
1700 outcount = OutputToProcess(pr, buf, newcount, outError);
1701 if (outcount < newcount) return -1; /* to be sure */
1708 } else if (((unsigned char) *p) == TN_IAC) {
1709 *q++ = (char) TN_IAC;
1716 if (appData.debugMode) {
1717 fprintf(debugFP, ">ICS: ");
1718 show_bytes(debugFP, buf, newcount);
1719 fprintf(debugFP, "\n");
1721 outcount = OutputToProcess(pr, buf, newcount, outError);
1722 if (outcount < newcount) return -1; /* to be sure */
1727 read_from_player(isr, closure, message, count, error)
1734 int outError, outCount;
1735 static int gotEof = 0;
1737 /* Pass data read from player on to ICS */
1740 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1741 if (outCount < count) {
1742 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1744 } else if (count < 0) {
1745 RemoveInputSource(isr);
1746 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1747 } else if (gotEof++ > 0) {
1748 RemoveInputSource(isr);
1749 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1755 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1756 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1757 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1758 SendToICS("date\n");
1759 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1762 /* added routine for printf style output to ics */
1763 void ics_printf(char *format, ...)
1765 char buffer[MSG_SIZ];
1768 va_start(args, format);
1769 vsnprintf(buffer, sizeof(buffer), format, args);
1770 buffer[sizeof(buffer)-1] = '\0';
1779 int count, outCount, outError;
1781 if (icsPR == NULL) return;
1784 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1785 if (outCount < count) {
1786 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1790 /* This is used for sending logon scripts to the ICS. Sending
1791 without a delay causes problems when using timestamp on ICC
1792 (at least on my machine). */
1794 SendToICSDelayed(s,msdelay)
1798 int count, outCount, outError;
1800 if (icsPR == NULL) return;
1803 if (appData.debugMode) {
1804 fprintf(debugFP, ">ICS: ");
1805 show_bytes(debugFP, s, count);
1806 fprintf(debugFP, "\n");
1808 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1816 /* Remove all highlighting escape sequences in s
1817 Also deletes any suffix starting with '('
1820 StripHighlightAndTitle(s)
1823 static char retbuf[MSG_SIZ];
1826 while (*s != NULLCHAR) {
1827 while (*s == '\033') {
1828 while (*s != NULLCHAR && !isalpha(*s)) s++;
1829 if (*s != NULLCHAR) s++;
1831 while (*s != NULLCHAR && *s != '\033') {
1832 if (*s == '(' || *s == '[') {
1843 /* Remove all highlighting escape sequences in s */
1848 static char retbuf[MSG_SIZ];
1851 while (*s != NULLCHAR) {
1852 while (*s == '\033') {
1853 while (*s != NULLCHAR && !isalpha(*s)) s++;
1854 if (*s != NULLCHAR) s++;
1856 while (*s != NULLCHAR && *s != '\033') {
1864 char *variantNames[] = VARIANT_NAMES;
1869 return variantNames[v];
1873 /* Identify a variant from the strings the chess servers use or the
1874 PGN Variant tag names we use. */
1881 VariantClass v = VariantNormal;
1882 int i, found = FALSE;
1888 /* [HGM] skip over optional board-size prefixes */
1889 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1890 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1891 while( *e++ != '_');
1894 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1898 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1899 if (StrCaseStr(e, variantNames[i])) {
1900 v = (VariantClass) i;
1907 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1908 || StrCaseStr(e, "wild/fr")
1909 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1910 v = VariantFischeRandom;
1911 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1912 (i = 1, p = StrCaseStr(e, "w"))) {
1914 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1921 case 0: /* FICS only, actually */
1923 /* Castling legal even if K starts on d-file */
1924 v = VariantWildCastle;
1929 /* Castling illegal even if K & R happen to start in
1930 normal positions. */
1931 v = VariantNoCastle;
1944 /* Castling legal iff K & R start in normal positions */
1950 /* Special wilds for position setup; unclear what to do here */
1951 v = VariantLoadable;
1954 /* Bizarre ICC game */
1955 v = VariantTwoKings;
1958 v = VariantKriegspiel;
1964 v = VariantFischeRandom;
1967 v = VariantCrazyhouse;
1970 v = VariantBughouse;
1976 /* Not quite the same as FICS suicide! */
1977 v = VariantGiveaway;
1983 v = VariantShatranj;
1986 /* Temporary names for future ICC types. The name *will* change in
1987 the next xboard/WinBoard release after ICC defines it. */
2025 v = VariantCapablanca;
2028 v = VariantKnightmate;
2034 v = VariantCylinder;
2040 v = VariantCapaRandom;
2043 v = VariantBerolina;
2055 /* Found "wild" or "w" in the string but no number;
2056 must assume it's normal chess. */
2060 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2061 if( (len > MSG_SIZ) && appData.debugMode )
2062 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2064 DisplayError(buf, 0);
2070 if (appData.debugMode) {
2071 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2072 e, wnum, VariantName(v));
2077 static int leftover_start = 0, leftover_len = 0;
2078 char star_match[STAR_MATCH_N][MSG_SIZ];
2080 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2081 advance *index beyond it, and set leftover_start to the new value of
2082 *index; else return FALSE. If pattern contains the character '*', it
2083 matches any sequence of characters not containing '\r', '\n', or the
2084 character following the '*' (if any), and the matched sequence(s) are
2085 copied into star_match.
2088 looking_at(buf, index, pattern)
2093 char *bufp = &buf[*index], *patternp = pattern;
2095 char *matchp = star_match[0];
2098 if (*patternp == NULLCHAR) {
2099 *index = leftover_start = bufp - buf;
2103 if (*bufp == NULLCHAR) return FALSE;
2104 if (*patternp == '*') {
2105 if (*bufp == *(patternp + 1)) {
2107 matchp = star_match[++star_count];
2111 } else if (*bufp == '\n' || *bufp == '\r') {
2113 if (*patternp == NULLCHAR)
2118 *matchp++ = *bufp++;
2122 if (*patternp != *bufp) return FALSE;
2129 SendToPlayer(data, length)
2133 int error, outCount;
2134 outCount = OutputToProcess(NoProc, data, length, &error);
2135 if (outCount < length) {
2136 DisplayFatalError(_("Error writing to display"), error, 1);
2141 PackHolding(packed, holding)
2153 switch (runlength) {
2164 sprintf(q, "%d", runlength);
2176 /* Telnet protocol requests from the front end */
2178 TelnetRequest(ddww, option)
2179 unsigned char ddww, option;
2181 unsigned char msg[3];
2182 int outCount, outError;
2184 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2186 if (appData.debugMode) {
2187 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2203 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2212 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2215 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2220 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2222 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2229 if (!appData.icsActive) return;
2230 TelnetRequest(TN_DO, TN_ECHO);
2236 if (!appData.icsActive) return;
2237 TelnetRequest(TN_DONT, TN_ECHO);
2241 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2243 /* put the holdings sent to us by the server on the board holdings area */
2244 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2248 if(gameInfo.holdingsWidth < 2) return;
2249 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2250 return; // prevent overwriting by pre-board holdings
2252 if( (int)lowestPiece >= BlackPawn ) {
2255 holdingsStartRow = BOARD_HEIGHT-1;
2258 holdingsColumn = BOARD_WIDTH-1;
2259 countsColumn = BOARD_WIDTH-2;
2260 holdingsStartRow = 0;
2264 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2265 board[i][holdingsColumn] = EmptySquare;
2266 board[i][countsColumn] = (ChessSquare) 0;
2268 while( (p=*holdings++) != NULLCHAR ) {
2269 piece = CharToPiece( ToUpper(p) );
2270 if(piece == EmptySquare) continue;
2271 /*j = (int) piece - (int) WhitePawn;*/
2272 j = PieceToNumber(piece);
2273 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2274 if(j < 0) continue; /* should not happen */
2275 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2276 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2277 board[holdingsStartRow+j*direction][countsColumn]++;
2283 VariantSwitch(Board board, VariantClass newVariant)
2285 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2286 static Board oldBoard;
2288 startedFromPositionFile = FALSE;
2289 if(gameInfo.variant == newVariant) return;
2291 /* [HGM] This routine is called each time an assignment is made to
2292 * gameInfo.variant during a game, to make sure the board sizes
2293 * are set to match the new variant. If that means adding or deleting
2294 * holdings, we shift the playing board accordingly
2295 * This kludge is needed because in ICS observe mode, we get boards
2296 * of an ongoing game without knowing the variant, and learn about the
2297 * latter only later. This can be because of the move list we requested,
2298 * in which case the game history is refilled from the beginning anyway,
2299 * but also when receiving holdings of a crazyhouse game. In the latter
2300 * case we want to add those holdings to the already received position.
2304 if (appData.debugMode) {
2305 fprintf(debugFP, "Switch board from %s to %s\n",
2306 VariantName(gameInfo.variant), VariantName(newVariant));
2307 setbuf(debugFP, NULL);
2309 shuffleOpenings = 0; /* [HGM] shuffle */
2310 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2314 newWidth = 9; newHeight = 9;
2315 gameInfo.holdingsSize = 7;
2316 case VariantBughouse:
2317 case VariantCrazyhouse:
2318 newHoldingsWidth = 2; break;
2322 newHoldingsWidth = 2;
2323 gameInfo.holdingsSize = 8;
2326 case VariantCapablanca:
2327 case VariantCapaRandom:
2330 newHoldingsWidth = gameInfo.holdingsSize = 0;
2333 if(newWidth != gameInfo.boardWidth ||
2334 newHeight != gameInfo.boardHeight ||
2335 newHoldingsWidth != gameInfo.holdingsWidth ) {
2337 /* shift position to new playing area, if needed */
2338 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2339 for(i=0; i<BOARD_HEIGHT; i++)
2340 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2341 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2343 for(i=0; i<newHeight; i++) {
2344 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2345 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2347 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2348 for(i=0; i<BOARD_HEIGHT; i++)
2349 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2350 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2353 gameInfo.boardWidth = newWidth;
2354 gameInfo.boardHeight = newHeight;
2355 gameInfo.holdingsWidth = newHoldingsWidth;
2356 gameInfo.variant = newVariant;
2357 InitDrawingSizes(-2, 0);
2358 } else gameInfo.variant = newVariant;
2359 CopyBoard(oldBoard, board); // remember correctly formatted board
2360 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2361 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2364 static int loggedOn = FALSE;
2366 /*-- Game start info cache: --*/
2368 char gs_kind[MSG_SIZ];
2369 static char player1Name[128] = "";
2370 static char player2Name[128] = "";
2371 static char cont_seq[] = "\n\\ ";
2372 static int player1Rating = -1;
2373 static int player2Rating = -1;
2374 /*----------------------------*/
2376 ColorClass curColor = ColorNormal;
2377 int suppressKibitz = 0;
2380 Boolean soughtPending = FALSE;
2381 Boolean seekGraphUp;
2382 #define MAX_SEEK_ADS 200
2384 char *seekAdList[MAX_SEEK_ADS];
2385 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2386 float tcList[MAX_SEEK_ADS];
2387 char colorList[MAX_SEEK_ADS];
2388 int nrOfSeekAds = 0;
2389 int minRating = 1010, maxRating = 2800;
2390 int hMargin = 10, vMargin = 20, h, w;
2391 extern int squareSize, lineGap;
2396 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2397 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2398 if(r < minRating+100 && r >=0 ) r = minRating+100;
2399 if(r > maxRating) r = maxRating;
2400 if(tc < 1.) tc = 1.;
2401 if(tc > 95.) tc = 95.;
2402 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2403 y = ((double)r - minRating)/(maxRating - minRating)
2404 * (h-vMargin-squareSize/8-1) + vMargin;
2405 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2406 if(strstr(seekAdList[i], " u ")) color = 1;
2407 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2408 !strstr(seekAdList[i], "bullet") &&
2409 !strstr(seekAdList[i], "blitz") &&
2410 !strstr(seekAdList[i], "standard") ) color = 2;
2411 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2412 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2416 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2418 char buf[MSG_SIZ], *ext = "";
2419 VariantClass v = StringToVariant(type);
2420 if(strstr(type, "wild")) {
2421 ext = type + 4; // append wild number
2422 if(v == VariantFischeRandom) type = "chess960"; else
2423 if(v == VariantLoadable) type = "setup"; else
2424 type = VariantName(v);
2426 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2427 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2428 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2429 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2430 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2431 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2432 seekNrList[nrOfSeekAds] = nr;
2433 zList[nrOfSeekAds] = 0;
2434 seekAdList[nrOfSeekAds++] = StrSave(buf);
2435 if(plot) PlotSeekAd(nrOfSeekAds-1);
2442 int x = xList[i], y = yList[i], d=squareSize/4, k;
2443 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2444 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2445 // now replot every dot that overlapped
2446 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2447 int xx = xList[k], yy = yList[k];
2448 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2449 DrawSeekDot(xx, yy, colorList[k]);
2454 RemoveSeekAd(int nr)
2457 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2459 if(seekAdList[i]) free(seekAdList[i]);
2460 seekAdList[i] = seekAdList[--nrOfSeekAds];
2461 seekNrList[i] = seekNrList[nrOfSeekAds];
2462 ratingList[i] = ratingList[nrOfSeekAds];
2463 colorList[i] = colorList[nrOfSeekAds];
2464 tcList[i] = tcList[nrOfSeekAds];
2465 xList[i] = xList[nrOfSeekAds];
2466 yList[i] = yList[nrOfSeekAds];
2467 zList[i] = zList[nrOfSeekAds];
2468 seekAdList[nrOfSeekAds] = NULL;
2474 MatchSoughtLine(char *line)
2476 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2477 int nr, base, inc, u=0; char dummy;
2479 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2480 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2482 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2483 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2484 // match: compact and save the line
2485 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2495 if(!seekGraphUp) return FALSE;
2496 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2497 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2499 DrawSeekBackground(0, 0, w, h);
2500 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2501 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2502 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2503 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2505 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2508 snprintf(buf, MSG_SIZ, "%d", i);
2509 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2512 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2513 for(i=1; i<100; i+=(i<10?1:5)) {
2514 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2515 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2516 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2518 snprintf(buf, MSG_SIZ, "%d", i);
2519 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2522 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2526 int SeekGraphClick(ClickType click, int x, int y, int moving)
2528 static int lastDown = 0, displayed = 0, lastSecond;
2529 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2530 if(click == Release || moving) return FALSE;
2532 soughtPending = TRUE;
2533 SendToICS(ics_prefix);
2534 SendToICS("sought\n"); // should this be "sought all"?
2535 } else { // issue challenge based on clicked ad
2536 int dist = 10000; int i, closest = 0, second = 0;
2537 for(i=0; i<nrOfSeekAds; i++) {
2538 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2539 if(d < dist) { dist = d; closest = i; }
2540 second += (d - zList[i] < 120); // count in-range ads
2541 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2545 second = (second > 1);
2546 if(displayed != closest || second != lastSecond) {
2547 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2548 lastSecond = second; displayed = closest;
2550 if(click == Press) {
2551 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2554 } // on press 'hit', only show info
2555 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2556 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2557 SendToICS(ics_prefix);
2559 return TRUE; // let incoming board of started game pop down the graph
2560 } else if(click == Release) { // release 'miss' is ignored
2561 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2562 if(moving == 2) { // right up-click
2563 nrOfSeekAds = 0; // refresh graph
2564 soughtPending = TRUE;
2565 SendToICS(ics_prefix);
2566 SendToICS("sought\n"); // should this be "sought all"?
2569 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2570 // press miss or release hit 'pop down' seek graph
2571 seekGraphUp = FALSE;
2572 DrawPosition(TRUE, NULL);
2578 read_from_ics(isr, closure, data, count, error)
2585 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2586 #define STARTED_NONE 0
2587 #define STARTED_MOVES 1
2588 #define STARTED_BOARD 2
2589 #define STARTED_OBSERVE 3
2590 #define STARTED_HOLDINGS 4
2591 #define STARTED_CHATTER 5
2592 #define STARTED_COMMENT 6
2593 #define STARTED_MOVES_NOHIDE 7
2595 static int started = STARTED_NONE;
2596 static char parse[20000];
2597 static int parse_pos = 0;
2598 static char buf[BUF_SIZE + 1];
2599 static int firstTime = TRUE, intfSet = FALSE;
2600 static ColorClass prevColor = ColorNormal;
2601 static int savingComment = FALSE;
2602 static int cmatch = 0; // continuation sequence match
2609 int backup; /* [DM] For zippy color lines */
2611 char talker[MSG_SIZ]; // [HGM] chat
2614 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2616 if (appData.debugMode) {
2618 fprintf(debugFP, "<ICS: ");
2619 show_bytes(debugFP, data, count);
2620 fprintf(debugFP, "\n");
2624 if (appData.debugMode) { int f = forwardMostMove;
2625 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2626 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2627 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2630 /* If last read ended with a partial line that we couldn't parse,
2631 prepend it to the new read and try again. */
2632 if (leftover_len > 0) {
2633 for (i=0; i<leftover_len; i++)
2634 buf[i] = buf[leftover_start + i];
2637 /* copy new characters into the buffer */
2638 bp = buf + leftover_len;
2639 buf_len=leftover_len;
2640 for (i=0; i<count; i++)
2643 if (data[i] == '\r')
2646 // join lines split by ICS?
2647 if (!appData.noJoin)
2650 Joining just consists of finding matches against the
2651 continuation sequence, and discarding that sequence
2652 if found instead of copying it. So, until a match
2653 fails, there's nothing to do since it might be the
2654 complete sequence, and thus, something we don't want
2657 if (data[i] == cont_seq[cmatch])
2660 if (cmatch == strlen(cont_seq))
2662 cmatch = 0; // complete match. just reset the counter
2665 it's possible for the ICS to not include the space
2666 at the end of the last word, making our [correct]
2667 join operation fuse two separate words. the server
2668 does this when the space occurs at the width setting.
2670 if (!buf_len || buf[buf_len-1] != ' ')
2681 match failed, so we have to copy what matched before
2682 falling through and copying this character. In reality,
2683 this will only ever be just the newline character, but
2684 it doesn't hurt to be precise.
2686 strncpy(bp, cont_seq, cmatch);
2698 buf[buf_len] = NULLCHAR;
2699 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2704 while (i < buf_len) {
2705 /* Deal with part of the TELNET option negotiation
2706 protocol. We refuse to do anything beyond the
2707 defaults, except that we allow the WILL ECHO option,
2708 which ICS uses to turn off password echoing when we are
2709 directly connected to it. We reject this option
2710 if localLineEditing mode is on (always on in xboard)
2711 and we are talking to port 23, which might be a real
2712 telnet server that will try to keep WILL ECHO on permanently.
2714 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2715 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2716 unsigned char option;
2718 switch ((unsigned char) buf[++i]) {
2720 if (appData.debugMode)
2721 fprintf(debugFP, "\n<WILL ");
2722 switch (option = (unsigned char) buf[++i]) {
2724 if (appData.debugMode)
2725 fprintf(debugFP, "ECHO ");
2726 /* Reply only if this is a change, according
2727 to the protocol rules. */
2728 if (remoteEchoOption) break;
2729 if (appData.localLineEditing &&
2730 atoi(appData.icsPort) == TN_PORT) {
2731 TelnetRequest(TN_DONT, TN_ECHO);
2734 TelnetRequest(TN_DO, TN_ECHO);
2735 remoteEchoOption = TRUE;
2739 if (appData.debugMode)
2740 fprintf(debugFP, "%d ", option);
2741 /* Whatever this is, we don't want it. */
2742 TelnetRequest(TN_DONT, option);
2747 if (appData.debugMode)
2748 fprintf(debugFP, "\n<WONT ");
2749 switch (option = (unsigned char) buf[++i]) {
2751 if (appData.debugMode)
2752 fprintf(debugFP, "ECHO ");
2753 /* Reply only if this is a change, according
2754 to the protocol rules. */
2755 if (!remoteEchoOption) break;
2757 TelnetRequest(TN_DONT, TN_ECHO);
2758 remoteEchoOption = FALSE;
2761 if (appData.debugMode)
2762 fprintf(debugFP, "%d ", (unsigned char) option);
2763 /* Whatever this is, it must already be turned
2764 off, because we never agree to turn on
2765 anything non-default, so according to the
2766 protocol rules, we don't reply. */
2771 if (appData.debugMode)
2772 fprintf(debugFP, "\n<DO ");
2773 switch (option = (unsigned char) buf[++i]) {
2775 /* Whatever this is, we refuse to do it. */
2776 if (appData.debugMode)
2777 fprintf(debugFP, "%d ", option);
2778 TelnetRequest(TN_WONT, option);
2783 if (appData.debugMode)
2784 fprintf(debugFP, "\n<DONT ");
2785 switch (option = (unsigned char) buf[++i]) {
2787 if (appData.debugMode)
2788 fprintf(debugFP, "%d ", option);
2789 /* Whatever this is, we are already not doing
2790 it, because we never agree to do anything
2791 non-default, so according to the protocol
2792 rules, we don't reply. */
2797 if (appData.debugMode)
2798 fprintf(debugFP, "\n<IAC ");
2799 /* Doubled IAC; pass it through */
2803 if (appData.debugMode)
2804 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2805 /* Drop all other telnet commands on the floor */
2808 if (oldi > next_out)
2809 SendToPlayer(&buf[next_out], oldi - next_out);
2815 /* OK, this at least will *usually* work */
2816 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2820 if (loggedOn && !intfSet) {
2821 if (ics_type == ICS_ICC) {
2822 snprintf(str, MSG_SIZ,
2823 "/set-quietly interface %s\n/set-quietly style 12\n",
2825 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2826 strcat(str, "/set-2 51 1\n/set seek 1\n");
2827 } else if (ics_type == ICS_CHESSNET) {
2828 snprintf(str, MSG_SIZ, "/style 12\n");
2830 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2831 strcat(str, programVersion);
2832 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2833 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2834 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2836 strcat(str, "$iset nohighlight 1\n");
2838 strcat(str, "$iset lock 1\n$style 12\n");
2841 NotifyFrontendLogin();
2845 if (started == STARTED_COMMENT) {
2846 /* Accumulate characters in comment */
2847 parse[parse_pos++] = buf[i];
2848 if (buf[i] == '\n') {
2849 parse[parse_pos] = NULLCHAR;
2850 if(chattingPartner>=0) {
2852 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2853 OutputChatMessage(chattingPartner, mess);
2854 chattingPartner = -1;
2855 next_out = i+1; // [HGM] suppress printing in ICS window
2857 if(!suppressKibitz) // [HGM] kibitz
2858 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2859 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2860 int nrDigit = 0, nrAlph = 0, j;
2861 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2862 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2863 parse[parse_pos] = NULLCHAR;
2864 // try to be smart: if it does not look like search info, it should go to
2865 // ICS interaction window after all, not to engine-output window.
2866 for(j=0; j<parse_pos; j++) { // count letters and digits
2867 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2868 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2869 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2871 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2872 int depth=0; float score;
2873 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2874 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2875 pvInfoList[forwardMostMove-1].depth = depth;
2876 pvInfoList[forwardMostMove-1].score = 100*score;
2878 OutputKibitz(suppressKibitz, parse);
2881 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2882 SendToPlayer(tmp, strlen(tmp));
2884 next_out = i+1; // [HGM] suppress printing in ICS window
2886 started = STARTED_NONE;
2888 /* Don't match patterns against characters in comment */
2893 if (started == STARTED_CHATTER) {
2894 if (buf[i] != '\n') {
2895 /* Don't match patterns against characters in chatter */
2899 started = STARTED_NONE;
2900 if(suppressKibitz) next_out = i+1;
2903 /* Kludge to deal with rcmd protocol */
2904 if (firstTime && looking_at(buf, &i, "\001*")) {
2905 DisplayFatalError(&buf[1], 0, 1);
2911 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2914 if (appData.debugMode)
2915 fprintf(debugFP, "ics_type %d\n", ics_type);
2918 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2919 ics_type = ICS_FICS;
2921 if (appData.debugMode)
2922 fprintf(debugFP, "ics_type %d\n", ics_type);
2925 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2926 ics_type = ICS_CHESSNET;
2928 if (appData.debugMode)
2929 fprintf(debugFP, "ics_type %d\n", ics_type);
2934 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2935 looking_at(buf, &i, "Logging you in as \"*\"") ||
2936 looking_at(buf, &i, "will be \"*\""))) {
2937 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2941 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2943 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2944 DisplayIcsInteractionTitle(buf);
2945 have_set_title = TRUE;
2948 /* skip finger notes */
2949 if (started == STARTED_NONE &&
2950 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2951 (buf[i] == '1' && buf[i+1] == '0')) &&
2952 buf[i+2] == ':' && buf[i+3] == ' ') {
2953 started = STARTED_CHATTER;
2959 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2960 if(appData.seekGraph) {
2961 if(soughtPending && MatchSoughtLine(buf+i)) {
2962 i = strstr(buf+i, "rated") - buf;
2963 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2964 next_out = leftover_start = i;
2965 started = STARTED_CHATTER;
2966 suppressKibitz = TRUE;
2969 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2970 && looking_at(buf, &i, "* ads displayed")) {
2971 soughtPending = FALSE;
2976 if(appData.autoRefresh) {
2977 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2978 int s = (ics_type == ICS_ICC); // ICC format differs
2980 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2981 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2982 looking_at(buf, &i, "*% "); // eat prompt
2983 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2984 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2985 next_out = i; // suppress
2988 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2989 char *p = star_match[0];
2991 if(seekGraphUp) RemoveSeekAd(atoi(p));
2992 while(*p && *p++ != ' '); // next
2994 looking_at(buf, &i, "*% "); // eat prompt
2995 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3002 /* skip formula vars */
3003 if (started == STARTED_NONE &&
3004 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3005 started = STARTED_CHATTER;
3010 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3011 if (appData.autoKibitz && started == STARTED_NONE &&
3012 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3013 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3014 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3015 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3016 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3017 suppressKibitz = TRUE;
3018 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3020 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3021 && (gameMode == IcsPlayingWhite)) ||
3022 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3023 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3024 started = STARTED_CHATTER; // own kibitz we simply discard
3026 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3027 parse_pos = 0; parse[0] = NULLCHAR;
3028 savingComment = TRUE;
3029 suppressKibitz = gameMode != IcsObserving ? 2 :
3030 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3034 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3035 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3036 && atoi(star_match[0])) {
3037 // suppress the acknowledgements of our own autoKibitz
3039 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3041 SendToPlayer(star_match[0], strlen(star_match[0]));
3042 if(looking_at(buf, &i, "*% ")) // eat prompt
3043 suppressKibitz = FALSE;
3047 } // [HGM] kibitz: end of patch
3049 // [HGM] chat: intercept tells by users for which we have an open chat window
3051 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3052 looking_at(buf, &i, "* whispers:") ||
3053 looking_at(buf, &i, "* kibitzes:") ||
3054 looking_at(buf, &i, "* shouts:") ||
3055 looking_at(buf, &i, "* c-shouts:") ||
3056 looking_at(buf, &i, "--> * ") ||
3057 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3058 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3059 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3060 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3062 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3063 chattingPartner = -1;
3065 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3066 for(p=0; p<MAX_CHAT; p++) {
3067 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3068 talker[0] = '['; strcat(talker, "] ");
3069 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3070 chattingPartner = p; break;
3073 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3074 for(p=0; p<MAX_CHAT; p++) {
3075 if(!strcmp("kibitzes", chatPartner[p])) {
3076 talker[0] = '['; strcat(talker, "] ");
3077 chattingPartner = p; break;
3080 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3081 for(p=0; p<MAX_CHAT; p++) {
3082 if(!strcmp("whispers", chatPartner[p])) {
3083 talker[0] = '['; strcat(talker, "] ");
3084 chattingPartner = p; break;
3087 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3088 if(buf[i-8] == '-' && buf[i-3] == 't')
3089 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3090 if(!strcmp("c-shouts", chatPartner[p])) {
3091 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3092 chattingPartner = p; break;
3095 if(chattingPartner < 0)
3096 for(p=0; p<MAX_CHAT; p++) {
3097 if(!strcmp("shouts", chatPartner[p])) {
3098 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3099 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3100 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3101 chattingPartner = p; break;
3105 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3106 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3107 talker[0] = 0; Colorize(ColorTell, FALSE);
3108 chattingPartner = p; break;
3110 if(chattingPartner<0) i = oldi; else {
3111 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3112 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3113 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3114 started = STARTED_COMMENT;
3115 parse_pos = 0; parse[0] = NULLCHAR;
3116 savingComment = 3 + chattingPartner; // counts as TRUE
3117 suppressKibitz = TRUE;
3120 } // [HGM] chat: end of patch
3123 if (appData.zippyTalk || appData.zippyPlay) {
3124 /* [DM] Backup address for color zippy lines */
3126 if (loggedOn == TRUE)
3127 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3128 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3130 } // [DM] 'else { ' deleted
3132 /* Regular tells and says */
3133 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3134 looking_at(buf, &i, "* (your partner) tells you: ") ||
3135 looking_at(buf, &i, "* says: ") ||
3136 /* Don't color "message" or "messages" output */
3137 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3138 looking_at(buf, &i, "*. * at *:*: ") ||
3139 looking_at(buf, &i, "--* (*:*): ") ||
3140 /* Message notifications (same color as tells) */
3141 looking_at(buf, &i, "* has left a message ") ||
3142 looking_at(buf, &i, "* just sent you a message:\n") ||
3143 /* Whispers and kibitzes */
3144 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3145 looking_at(buf, &i, "* kibitzes: ") ||
3147 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3149 if (tkind == 1 && strchr(star_match[0], ':')) {
3150 /* Avoid "tells you:" spoofs in channels */
3153 if (star_match[0][0] == NULLCHAR ||
3154 strchr(star_match[0], ' ') ||
3155 (tkind == 3 && strchr(star_match[1], ' '))) {
3156 /* Reject bogus matches */
3159 if (appData.colorize) {
3160 if (oldi > next_out) {
3161 SendToPlayer(&buf[next_out], oldi - next_out);
3166 Colorize(ColorTell, FALSE);
3167 curColor = ColorTell;
3170 Colorize(ColorKibitz, FALSE);
3171 curColor = ColorKibitz;
3174 p = strrchr(star_match[1], '(');
3181 Colorize(ColorChannel1, FALSE);
3182 curColor = ColorChannel1;
3184 Colorize(ColorChannel, FALSE);
3185 curColor = ColorChannel;
3189 curColor = ColorNormal;
3193 if (started == STARTED_NONE && appData.autoComment &&
3194 (gameMode == IcsObserving ||
3195 gameMode == IcsPlayingWhite ||
3196 gameMode == IcsPlayingBlack)) {
3197 parse_pos = i - oldi;
3198 memcpy(parse, &buf[oldi], parse_pos);
3199 parse[parse_pos] = NULLCHAR;
3200 started = STARTED_COMMENT;
3201 savingComment = TRUE;
3203 started = STARTED_CHATTER;
3204 savingComment = FALSE;
3211 if (looking_at(buf, &i, "* s-shouts: ") ||
3212 looking_at(buf, &i, "* c-shouts: ")) {
3213 if (appData.colorize) {
3214 if (oldi > next_out) {
3215 SendToPlayer(&buf[next_out], oldi - next_out);
3218 Colorize(ColorSShout, FALSE);
3219 curColor = ColorSShout;
3222 started = STARTED_CHATTER;
3226 if (looking_at(buf, &i, "--->")) {
3231 if (looking_at(buf, &i, "* shouts: ") ||
3232 looking_at(buf, &i, "--> ")) {
3233 if (appData.colorize) {
3234 if (oldi > next_out) {
3235 SendToPlayer(&buf[next_out], oldi - next_out);
3238 Colorize(ColorShout, FALSE);
3239 curColor = ColorShout;
3242 started = STARTED_CHATTER;
3246 if (looking_at( buf, &i, "Challenge:")) {
3247 if (appData.colorize) {
3248 if (oldi > next_out) {
3249 SendToPlayer(&buf[next_out], oldi - next_out);
3252 Colorize(ColorChallenge, FALSE);
3253 curColor = ColorChallenge;
3259 if (looking_at(buf, &i, "* offers you") ||
3260 looking_at(buf, &i, "* offers to be") ||
3261 looking_at(buf, &i, "* would like to") ||
3262 looking_at(buf, &i, "* requests to") ||
3263 looking_at(buf, &i, "Your opponent offers") ||
3264 looking_at(buf, &i, "Your opponent requests")) {
3266 if (appData.colorize) {
3267 if (oldi > next_out) {
3268 SendToPlayer(&buf[next_out], oldi - next_out);
3271 Colorize(ColorRequest, FALSE);
3272 curColor = ColorRequest;
3277 if (looking_at(buf, &i, "* (*) seeking")) {
3278 if (appData.colorize) {
3279 if (oldi > next_out) {
3280 SendToPlayer(&buf[next_out], oldi - next_out);
3283 Colorize(ColorSeek, FALSE);
3284 curColor = ColorSeek;
3289 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3291 if (looking_at(buf, &i, "\\ ")) {
3292 if (prevColor != ColorNormal) {
3293 if (oldi > next_out) {
3294 SendToPlayer(&buf[next_out], oldi - next_out);
3297 Colorize(prevColor, TRUE);
3298 curColor = prevColor;
3300 if (savingComment) {
3301 parse_pos = i - oldi;
3302 memcpy(parse, &buf[oldi], parse_pos);
3303 parse[parse_pos] = NULLCHAR;
3304 started = STARTED_COMMENT;
3305 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3306 chattingPartner = savingComment - 3; // kludge to remember the box
3308 started = STARTED_CHATTER;
3313 if (looking_at(buf, &i, "Black Strength :") ||
3314 looking_at(buf, &i, "<<< style 10 board >>>") ||
3315 looking_at(buf, &i, "<10>") ||
3316 looking_at(buf, &i, "#@#")) {
3317 /* Wrong board style */
3319 SendToICS(ics_prefix);
3320 SendToICS("set style 12\n");
3321 SendToICS(ics_prefix);
3322 SendToICS("refresh\n");
3326 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3328 have_sent_ICS_logon = 1;
3332 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3333 (looking_at(buf, &i, "\n<12> ") ||
3334 looking_at(buf, &i, "<12> "))) {
3336 if (oldi > next_out) {
3337 SendToPlayer(&buf[next_out], oldi - next_out);
3340 started = STARTED_BOARD;
3345 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3346 looking_at(buf, &i, "<b1> ")) {
3347 if (oldi > next_out) {
3348 SendToPlayer(&buf[next_out], oldi - next_out);
3351 started = STARTED_HOLDINGS;
3356 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3358 /* Header for a move list -- first line */
3360 switch (ics_getting_history) {
3364 case BeginningOfGame:
3365 /* User typed "moves" or "oldmoves" while we
3366 were idle. Pretend we asked for these
3367 moves and soak them up so user can step
3368 through them and/or save them.
3371 gameMode = IcsObserving;
3374 ics_getting_history = H_GOT_UNREQ_HEADER;
3376 case EditGame: /*?*/
3377 case EditPosition: /*?*/
3378 /* Should above feature work in these modes too? */
3379 /* For now it doesn't */
3380 ics_getting_history = H_GOT_UNWANTED_HEADER;
3383 ics_getting_history = H_GOT_UNWANTED_HEADER;
3388 /* Is this the right one? */
3389 if (gameInfo.white && gameInfo.black &&
3390 strcmp(gameInfo.white, star_match[0]) == 0 &&
3391 strcmp(gameInfo.black, star_match[2]) == 0) {
3393 ics_getting_history = H_GOT_REQ_HEADER;
3396 case H_GOT_REQ_HEADER:
3397 case H_GOT_UNREQ_HEADER:
3398 case H_GOT_UNWANTED_HEADER:
3399 case H_GETTING_MOVES:
3400 /* Should not happen */
3401 DisplayError(_("Error gathering move list: two headers"), 0);
3402 ics_getting_history = H_FALSE;
3406 /* Save player ratings into gameInfo if needed */
3407 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3408 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3409 (gameInfo.whiteRating == -1 ||
3410 gameInfo.blackRating == -1)) {
3412 gameInfo.whiteRating = string_to_rating(star_match[1]);
3413 gameInfo.blackRating = string_to_rating(star_match[3]);
3414 if (appData.debugMode)
3415 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3416 gameInfo.whiteRating, gameInfo.blackRating);
3421 if (looking_at(buf, &i,
3422 "* * match, initial time: * minute*, increment: * second")) {
3423 /* Header for a move list -- second line */
3424 /* Initial board will follow if this is a wild game */
3425 if (gameInfo.event != NULL) free(gameInfo.event);
3426 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3427 gameInfo.event = StrSave(str);
3428 /* [HGM] we switched variant. Translate boards if needed. */
3429 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3433 if (looking_at(buf, &i, "Move ")) {
3434 /* Beginning of a move list */
3435 switch (ics_getting_history) {
3437 /* Normally should not happen */
3438 /* Maybe user hit reset while we were parsing */
3441 /* Happens if we are ignoring a move list that is not
3442 * the one we just requested. Common if the user
3443 * tries to observe two games without turning off
3446 case H_GETTING_MOVES:
3447 /* Should not happen */
3448 DisplayError(_("Error gathering move list: nested"), 0);
3449 ics_getting_history = H_FALSE;
3451 case H_GOT_REQ_HEADER:
3452 ics_getting_history = H_GETTING_MOVES;
3453 started = STARTED_MOVES;
3455 if (oldi > next_out) {
3456 SendToPlayer(&buf[next_out], oldi - next_out);
3459 case H_GOT_UNREQ_HEADER:
3460 ics_getting_history = H_GETTING_MOVES;
3461 started = STARTED_MOVES_NOHIDE;
3464 case H_GOT_UNWANTED_HEADER:
3465 ics_getting_history = H_FALSE;
3471 if (looking_at(buf, &i, "% ") ||
3472 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3473 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3474 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3475 soughtPending = FALSE;
3479 if(suppressKibitz) next_out = i;
3480 savingComment = FALSE;
3484 case STARTED_MOVES_NOHIDE:
3485 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3486 parse[parse_pos + i - oldi] = NULLCHAR;
3487 ParseGameHistory(parse);
3489 if (appData.zippyPlay && first.initDone) {
3490 FeedMovesToProgram(&first, forwardMostMove);
3491 if (gameMode == IcsPlayingWhite) {
3492 if (WhiteOnMove(forwardMostMove)) {
3493 if (first.sendTime) {
3494 if (first.useColors) {
3495 SendToProgram("black\n", &first);
3497 SendTimeRemaining(&first, TRUE);
3499 if (first.useColors) {
3500 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3502 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3503 first.maybeThinking = TRUE;
3505 if (first.usePlayother) {
3506 if (first.sendTime) {
3507 SendTimeRemaining(&first, TRUE);
3509 SendToProgram("playother\n", &first);
3515 } else if (gameMode == IcsPlayingBlack) {
3516 if (!WhiteOnMove(forwardMostMove)) {
3517 if (first.sendTime) {
3518 if (first.useColors) {
3519 SendToProgram("white\n", &first);
3521 SendTimeRemaining(&first, FALSE);
3523 if (first.useColors) {
3524 SendToProgram("black\n", &first);
3526 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3527 first.maybeThinking = TRUE;
3529 if (first.usePlayother) {
3530 if (first.sendTime) {
3531 SendTimeRemaining(&first, FALSE);
3533 SendToProgram("playother\n", &first);
3542 if (gameMode == IcsObserving && ics_gamenum == -1) {
3543 /* Moves came from oldmoves or moves command
3544 while we weren't doing anything else.
3546 currentMove = forwardMostMove;
3547 ClearHighlights();/*!!could figure this out*/
3548 flipView = appData.flipView;
3549 DrawPosition(TRUE, boards[currentMove]);
3550 DisplayBothClocks();
3551 snprintf(str, MSG_SIZ, "%s vs. %s",
3552 gameInfo.white, gameInfo.black);
3556 /* Moves were history of an active game */
3557 if (gameInfo.resultDetails != NULL) {
3558 free(gameInfo.resultDetails);
3559 gameInfo.resultDetails = NULL;
3562 HistorySet(parseList, backwardMostMove,
3563 forwardMostMove, currentMove-1);
3564 DisplayMove(currentMove - 1);
3565 if (started == STARTED_MOVES) next_out = i;
3566 started = STARTED_NONE;
3567 ics_getting_history = H_FALSE;
3570 case STARTED_OBSERVE:
3571 started = STARTED_NONE;
3572 SendToICS(ics_prefix);
3573 SendToICS("refresh\n");
3579 if(bookHit) { // [HGM] book: simulate book reply
3580 static char bookMove[MSG_SIZ]; // a bit generous?
3582 programStats.nodes = programStats.depth = programStats.time =
3583 programStats.score = programStats.got_only_move = 0;
3584 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3586 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3587 strcat(bookMove, bookHit);
3588 HandleMachineMove(bookMove, &first);
3593 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3594 started == STARTED_HOLDINGS ||
3595 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3596 /* Accumulate characters in move list or board */
3597 parse[parse_pos++] = buf[i];
3600 /* Start of game messages. Mostly we detect start of game
3601 when the first board image arrives. On some versions
3602 of the ICS, though, we need to do a "refresh" after starting
3603 to observe in order to get the current board right away. */
3604 if (looking_at(buf, &i, "Adding game * to observation list")) {
3605 started = STARTED_OBSERVE;
3609 /* Handle auto-observe */
3610 if (appData.autoObserve &&
3611 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3612 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3614 /* Choose the player that was highlighted, if any. */
3615 if (star_match[0][0] == '\033' ||
3616 star_match[1][0] != '\033') {
3617 player = star_match[0];
3619 player = star_match[2];
3621 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3622 ics_prefix, StripHighlightAndTitle(player));
3625 /* Save ratings from notify string */
3626 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3627 player1Rating = string_to_rating(star_match[1]);
3628 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3629 player2Rating = string_to_rating(star_match[3]);
3631 if (appData.debugMode)
3633 "Ratings from 'Game notification:' %s %d, %s %d\n",
3634 player1Name, player1Rating,
3635 player2Name, player2Rating);
3640 /* Deal with automatic examine mode after a game,
3641 and with IcsObserving -> IcsExamining transition */
3642 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3643 looking_at(buf, &i, "has made you an examiner of game *")) {
3645 int gamenum = atoi(star_match[0]);
3646 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3647 gamenum == ics_gamenum) {
3648 /* We were already playing or observing this game;
3649 no need to refetch history */
3650 gameMode = IcsExamining;
3652 pauseExamForwardMostMove = forwardMostMove;
3653 } else if (currentMove < forwardMostMove) {
3654 ForwardInner(forwardMostMove);
3657 /* I don't think this case really can happen */
3658 SendToICS(ics_prefix);
3659 SendToICS("refresh\n");
3664 /* Error messages */
3665 // if (ics_user_moved) {
3666 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3667 if (looking_at(buf, &i, "Illegal move") ||
3668 looking_at(buf, &i, "Not a legal move") ||
3669 looking_at(buf, &i, "Your king is in check") ||
3670 looking_at(buf, &i, "It isn't your turn") ||
3671 looking_at(buf, &i, "It is not your move")) {
3673 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3674 currentMove = forwardMostMove-1;
3675 DisplayMove(currentMove - 1); /* before DMError */
3676 DrawPosition(FALSE, boards[currentMove]);
3677 SwitchClocks(forwardMostMove-1); // [HGM] race
3678 DisplayBothClocks();
3680 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3686 if (looking_at(buf, &i, "still have time") ||
3687 looking_at(buf, &i, "not out of time") ||
3688 looking_at(buf, &i, "either player is out of time") ||
3689 looking_at(buf, &i, "has timeseal; checking")) {
3690 /* We must have called his flag a little too soon */
3691 whiteFlag = blackFlag = FALSE;
3695 if (looking_at(buf, &i, "added * seconds to") ||
3696 looking_at(buf, &i, "seconds were added to")) {
3697 /* Update the clocks */
3698 SendToICS(ics_prefix);
3699 SendToICS("refresh\n");
3703 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3704 ics_clock_paused = TRUE;
3709 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3710 ics_clock_paused = FALSE;
3715 /* Grab player ratings from the Creating: message.
3716 Note we have to check for the special case when
3717 the ICS inserts things like [white] or [black]. */
3718 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3719 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3721 0 player 1 name (not necessarily white)
3723 2 empty, white, or black (IGNORED)
3724 3 player 2 name (not necessarily black)
3727 The names/ratings are sorted out when the game
3728 actually starts (below).
3730 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3731 player1Rating = string_to_rating(star_match[1]);
3732 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3733 player2Rating = string_to_rating(star_match[4]);
3735 if (appData.debugMode)
3737 "Ratings from 'Creating:' %s %d, %s %d\n",
3738 player1Name, player1Rating,
3739 player2Name, player2Rating);
3744 /* Improved generic start/end-of-game messages */
3745 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3746 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3747 /* If tkind == 0: */
3748 /* star_match[0] is the game number */
3749 /* [1] is the white player's name */
3750 /* [2] is the black player's name */
3751 /* For end-of-game: */
3752 /* [3] is the reason for the game end */
3753 /* [4] is a PGN end game-token, preceded by " " */
3754 /* For start-of-game: */
3755 /* [3] begins with "Creating" or "Continuing" */
3756 /* [4] is " *" or empty (don't care). */
3757 int gamenum = atoi(star_match[0]);
3758 char *whitename, *blackname, *why, *endtoken;
3759 ChessMove endtype = EndOfFile;
3762 whitename = star_match[1];
3763 blackname = star_match[2];
3764 why = star_match[3];
3765 endtoken = star_match[4];
3767 whitename = star_match[1];
3768 blackname = star_match[3];
3769 why = star_match[5];
3770 endtoken = star_match[6];
3773 /* Game start messages */
3774 if (strncmp(why, "Creating ", 9) == 0 ||
3775 strncmp(why, "Continuing ", 11) == 0) {
3776 gs_gamenum = gamenum;
3777 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3778 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3780 if (appData.zippyPlay) {
3781 ZippyGameStart(whitename, blackname);
3784 partnerBoardValid = FALSE; // [HGM] bughouse
3788 /* Game end messages */
3789 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3790 ics_gamenum != gamenum) {
3793 while (endtoken[0] == ' ') endtoken++;
3794 switch (endtoken[0]) {
3797 endtype = GameUnfinished;
3800 endtype = BlackWins;
3803 if (endtoken[1] == '/')
3804 endtype = GameIsDrawn;
3806 endtype = WhiteWins;
3809 GameEnds(endtype, why, GE_ICS);
3811 if (appData.zippyPlay && first.initDone) {
3812 ZippyGameEnd(endtype, why);
3813 if (first.pr == NULL) {
3814 /* Start the next process early so that we'll
3815 be ready for the next challenge */
3816 StartChessProgram(&first);
3818 /* Send "new" early, in case this command takes
3819 a long time to finish, so that we'll be ready
3820 for the next challenge. */
3821 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3825 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3829 if (looking_at(buf, &i, "Removing game * from observation") ||
3830 looking_at(buf, &i, "no longer observing game *") ||
3831 looking_at(buf, &i, "Game * (*) has no examiners")) {
3832 if (gameMode == IcsObserving &&
3833 atoi(star_match[0]) == ics_gamenum)
3835 /* icsEngineAnalyze */
3836 if (appData.icsEngineAnalyze) {
3843 ics_user_moved = FALSE;
3848 if (looking_at(buf, &i, "no longer examining game *")) {
3849 if (gameMode == IcsExamining &&
3850 atoi(star_match[0]) == ics_gamenum)
3854 ics_user_moved = FALSE;
3859 /* Advance leftover_start past any newlines we find,
3860 so only partial lines can get reparsed */
3861 if (looking_at(buf, &i, "\n")) {
3862 prevColor = curColor;
3863 if (curColor != ColorNormal) {
3864 if (oldi > next_out) {
3865 SendToPlayer(&buf[next_out], oldi - next_out);
3868 Colorize(ColorNormal, FALSE);
3869 curColor = ColorNormal;
3871 if (started == STARTED_BOARD) {
3872 started = STARTED_NONE;
3873 parse[parse_pos] = NULLCHAR;
3874 ParseBoard12(parse);
3877 /* Send premove here */
3878 if (appData.premove) {
3880 if (currentMove == 0 &&
3881 gameMode == IcsPlayingWhite &&
3882 appData.premoveWhite) {
3883 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3884 if (appData.debugMode)
3885 fprintf(debugFP, "Sending premove:\n");
3887 } else if (currentMove == 1 &&
3888 gameMode == IcsPlayingBlack &&
3889 appData.premoveBlack) {
3890 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3891 if (appData.debugMode)
3892 fprintf(debugFP, "Sending premove:\n");
3894 } else if (gotPremove) {
3896 ClearPremoveHighlights();
3897 if (appData.debugMode)
3898 fprintf(debugFP, "Sending premove:\n");
3899 UserMoveEvent(premoveFromX, premoveFromY,
3900 premoveToX, premoveToY,
3905 /* Usually suppress following prompt */
3906 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3907 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3908 if (looking_at(buf, &i, "*% ")) {
3909 savingComment = FALSE;
3914 } else if (started == STARTED_HOLDINGS) {
3916 char new_piece[MSG_SIZ];
3917 started = STARTED_NONE;
3918 parse[parse_pos] = NULLCHAR;
3919 if (appData.debugMode)
3920 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3921 parse, currentMove);
3922 if (sscanf(parse, " game %d", &gamenum) == 1) {
3923 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3924 if (gameInfo.variant == VariantNormal) {
3925 /* [HGM] We seem to switch variant during a game!
3926 * Presumably no holdings were displayed, so we have
3927 * to move the position two files to the right to
3928 * create room for them!
3930 VariantClass newVariant;
3931 switch(gameInfo.boardWidth) { // base guess on board width
3932 case 9: newVariant = VariantShogi; break;
3933 case 10: newVariant = VariantGreat; break;
3934 default: newVariant = VariantCrazyhouse; break;
3936 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3937 /* Get a move list just to see the header, which
3938 will tell us whether this is really bug or zh */
3939 if (ics_getting_history == H_FALSE) {
3940 ics_getting_history = H_REQUESTED;
3941 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3945 new_piece[0] = NULLCHAR;
3946 sscanf(parse, "game %d white [%s black [%s <- %s",
3947 &gamenum, white_holding, black_holding,
3949 white_holding[strlen(white_holding)-1] = NULLCHAR;
3950 black_holding[strlen(black_holding)-1] = NULLCHAR;
3951 /* [HGM] copy holdings to board holdings area */
3952 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3953 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3954 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3956 if (appData.zippyPlay && first.initDone) {
3957 ZippyHoldings(white_holding, black_holding,
3961 if (tinyLayout || smallLayout) {
3962 char wh[16], bh[16];
3963 PackHolding(wh, white_holding);
3964 PackHolding(bh, black_holding);
3965 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3966 gameInfo.white, gameInfo.black);
3968 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3969 gameInfo.white, white_holding,
3970 gameInfo.black, black_holding);
3972 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3973 DrawPosition(FALSE, boards[currentMove]);
3975 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3976 sscanf(parse, "game %d white [%s black [%s <- %s",
3977 &gamenum, white_holding, black_holding,
3979 white_holding[strlen(white_holding)-1] = NULLCHAR;
3980 black_holding[strlen(black_holding)-1] = NULLCHAR;
3981 /* [HGM] copy holdings to partner-board holdings area */
3982 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3983 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3984 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3985 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3986 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3989 /* Suppress following prompt */
3990 if (looking_at(buf, &i, "*% ")) {
3991 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3992 savingComment = FALSE;
4000 i++; /* skip unparsed character and loop back */
4003 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4004 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4005 // SendToPlayer(&buf[next_out], i - next_out);
4006 started != STARTED_HOLDINGS && leftover_start > next_out) {
4007 SendToPlayer(&buf[next_out], leftover_start - next_out);
4011 leftover_len = buf_len - leftover_start;
4012 /* if buffer ends with something we couldn't parse,
4013 reparse it after appending the next read */
4015 } else if (count == 0) {
4016 RemoveInputSource(isr);
4017 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4019 DisplayFatalError(_("Error reading from ICS"), error, 1);
4024 /* Board style 12 looks like this:
4026 <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
4028 * The "<12> " is stripped before it gets to this routine. The two
4029 * trailing 0's (flip state and clock ticking) are later addition, and
4030 * some chess servers may not have them, or may have only the first.
4031 * Additional trailing fields may be added in the future.
4034 #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"
4036 #define RELATION_OBSERVING_PLAYED 0
4037 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4038 #define RELATION_PLAYING_MYMOVE 1
4039 #define RELATION_PLAYING_NOTMYMOVE -1
4040 #define RELATION_EXAMINING 2
4041 #define RELATION_ISOLATED_BOARD -3
4042 #define RELATION_STARTING_POSITION -4 /* FICS only */
4045 ParseBoard12(string)
4048 GameMode newGameMode;
4049 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4050 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4051 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4052 char to_play, board_chars[200];
4053 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4054 char black[32], white[32];
4056 int prevMove = currentMove;
4059 int fromX, fromY, toX, toY;
4061 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4062 char *bookHit = NULL; // [HGM] book
4063 Boolean weird = FALSE, reqFlag = FALSE;
4065 fromX = fromY = toX = toY = -1;
4069 if (appData.debugMode)
4070 fprintf(debugFP, _("Parsing board: %s\n"), string);
4072 move_str[0] = NULLCHAR;
4073 elapsed_time[0] = NULLCHAR;
4074 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4076 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4077 if(string[i] == ' ') { ranks++; files = 0; }
4079 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4082 for(j = 0; j <i; j++) board_chars[j] = string[j];
4083 board_chars[i] = '\0';
4086 n = sscanf(string, PATTERN, &to_play, &double_push,
4087 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4088 &gamenum, white, black, &relation, &basetime, &increment,
4089 &white_stren, &black_stren, &white_time, &black_time,
4090 &moveNum, str, elapsed_time, move_str, &ics_flip,
4094 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4095 DisplayError(str, 0);
4099 /* Convert the move number to internal form */
4100 moveNum = (moveNum - 1) * 2;
4101 if (to_play == 'B') moveNum++;
4102 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4103 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4109 case RELATION_OBSERVING_PLAYED:
4110 case RELATION_OBSERVING_STATIC:
4111 if (gamenum == -1) {
4112 /* Old ICC buglet */
4113 relation = RELATION_OBSERVING_STATIC;
4115 newGameMode = IcsObserving;
4117 case RELATION_PLAYING_MYMOVE:
4118 case RELATION_PLAYING_NOTMYMOVE:
4120 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4121 IcsPlayingWhite : IcsPlayingBlack;
4123 case RELATION_EXAMINING:
4124 newGameMode = IcsExamining;
4126 case RELATION_ISOLATED_BOARD:
4128 /* Just display this board. If user was doing something else,
4129 we will forget about it until the next board comes. */
4130 newGameMode = IcsIdle;
4132 case RELATION_STARTING_POSITION:
4133 newGameMode = gameMode;
4137 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4138 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4139 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4141 for (k = 0; k < ranks; k++) {
4142 for (j = 0; j < files; j++)
4143 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4144 if(gameInfo.holdingsWidth > 1) {
4145 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4146 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4149 CopyBoard(partnerBoard, board);
4150 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4151 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4152 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4153 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4154 if(toSqr = strchr(str, '-')) {
4155 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4156 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4157 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4158 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4159 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4160 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4161 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4162 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4163 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4164 DisplayMessage(partnerStatus, "");
4165 partnerBoardValid = TRUE;
4169 /* Modify behavior for initial board display on move listing
4172 switch (ics_getting_history) {
4176 case H_GOT_REQ_HEADER:
4177 case H_GOT_UNREQ_HEADER:
4178 /* This is the initial position of the current game */
4179 gamenum = ics_gamenum;
4180 moveNum = 0; /* old ICS bug workaround */
4181 if (to_play == 'B') {
4182 startedFromSetupPosition = TRUE;
4183 blackPlaysFirst = TRUE;
4185 if (forwardMostMove == 0) forwardMostMove = 1;
4186 if (backwardMostMove == 0) backwardMostMove = 1;
4187 if (currentMove == 0) currentMove = 1;
4189 newGameMode = gameMode;
4190 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4192 case H_GOT_UNWANTED_HEADER:
4193 /* This is an initial board that we don't want */
4195 case H_GETTING_MOVES:
4196 /* Should not happen */
4197 DisplayError(_("Error gathering move list: extra board"), 0);
4198 ics_getting_history = H_FALSE;
4202 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4203 weird && (int)gameInfo.variant < (int)VariantShogi) {
4204 /* [HGM] We seem to have switched variant unexpectedly
4205 * Try to guess new variant from board size
4207 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4208 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4209 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4210 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4211 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4212 if(!weird) newVariant = VariantNormal;
4213 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4214 /* Get a move list just to see the header, which
4215 will tell us whether this is really bug or zh */
4216 if (ics_getting_history == H_FALSE) {
4217 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4218 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4223 /* Take action if this is the first board of a new game, or of a
4224 different game than is currently being displayed. */
4225 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4226 relation == RELATION_ISOLATED_BOARD) {
4228 /* Forget the old game and get the history (if any) of the new one */
4229 if (gameMode != BeginningOfGame) {
4233 if (appData.autoRaiseBoard) BoardToTop();
4235 if (gamenum == -1) {
4236 newGameMode = IcsIdle;
4237 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4238 appData.getMoveList && !reqFlag) {
4239 /* Need to get game history */
4240 ics_getting_history = H_REQUESTED;
4241 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4245 /* Initially flip the board to have black on the bottom if playing
4246 black or if the ICS flip flag is set, but let the user change
4247 it with the Flip View button. */
4248 flipView = appData.autoFlipView ?
4249 (newGameMode == IcsPlayingBlack) || ics_flip :
4252 /* Done with values from previous mode; copy in new ones */
4253 gameMode = newGameMode;
4255 ics_gamenum = gamenum;
4256 if (gamenum == gs_gamenum) {
4257 int klen = strlen(gs_kind);
4258 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4259 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4260 gameInfo.event = StrSave(str);
4262 gameInfo.event = StrSave("ICS game");
4264 gameInfo.site = StrSave(appData.icsHost);
4265 gameInfo.date = PGNDate();
4266 gameInfo.round = StrSave("-");
4267 gameInfo.white = StrSave(white);
4268 gameInfo.black = StrSave(black);
4269 timeControl = basetime * 60 * 1000;
4271 timeIncrement = increment * 1000;
4272 movesPerSession = 0;
4273 gameInfo.timeControl = TimeControlTagValue();
4274 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4275 if (appData.debugMode) {
4276 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4277 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4278 setbuf(debugFP, NULL);
4281 gameInfo.outOfBook = NULL;
4283 /* Do we have the ratings? */
4284 if (strcmp(player1Name, white) == 0 &&
4285 strcmp(player2Name, black) == 0) {
4286 if (appData.debugMode)
4287 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4288 player1Rating, player2Rating);
4289 gameInfo.whiteRating = player1Rating;
4290 gameInfo.blackRating = player2Rating;
4291 } else if (strcmp(player2Name, white) == 0 &&
4292 strcmp(player1Name, black) == 0) {
4293 if (appData.debugMode)
4294 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4295 player2Rating, player1Rating);
4296 gameInfo.whiteRating = player2Rating;
4297 gameInfo.blackRating = player1Rating;
4299 player1Name[0] = player2Name[0] = NULLCHAR;
4301 /* Silence shouts if requested */
4302 if (appData.quietPlay &&
4303 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4304 SendToICS(ics_prefix);
4305 SendToICS("set shout 0\n");
4309 /* Deal with midgame name changes */
4311 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4312 if (gameInfo.white) free(gameInfo.white);
4313 gameInfo.white = StrSave(white);
4315 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4316 if (gameInfo.black) free(gameInfo.black);
4317 gameInfo.black = StrSave(black);
4321 /* Throw away game result if anything actually changes in examine mode */
4322 if (gameMode == IcsExamining && !newGame) {
4323 gameInfo.result = GameUnfinished;
4324 if (gameInfo.resultDetails != NULL) {
4325 free(gameInfo.resultDetails);
4326 gameInfo.resultDetails = NULL;
4330 /* In pausing && IcsExamining mode, we ignore boards coming
4331 in if they are in a different variation than we are. */
4332 if (pauseExamInvalid) return;
4333 if (pausing && gameMode == IcsExamining) {
4334 if (moveNum <= pauseExamForwardMostMove) {
4335 pauseExamInvalid = TRUE;
4336 forwardMostMove = pauseExamForwardMostMove;
4341 if (appData.debugMode) {
4342 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4344 /* Parse the board */
4345 for (k = 0; k < ranks; k++) {
4346 for (j = 0; j < files; j++)
4347 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4348 if(gameInfo.holdingsWidth > 1) {
4349 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4350 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4353 CopyBoard(boards[moveNum], board);
4354 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4356 startedFromSetupPosition =
4357 !CompareBoards(board, initialPosition);
4358 if(startedFromSetupPosition)
4359 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4362 /* [HGM] Set castling rights. Take the outermost Rooks,
4363 to make it also work for FRC opening positions. Note that board12
4364 is really defective for later FRC positions, as it has no way to
4365 indicate which Rook can castle if they are on the same side of King.
4366 For the initial position we grant rights to the outermost Rooks,
4367 and remember thos rights, and we then copy them on positions
4368 later in an FRC game. This means WB might not recognize castlings with
4369 Rooks that have moved back to their original position as illegal,
4370 but in ICS mode that is not its job anyway.
4372 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4373 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4375 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4376 if(board[0][i] == WhiteRook) j = i;
4377 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4378 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4379 if(board[0][i] == WhiteRook) j = i;
4380 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4381 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4382 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4383 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4384 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4385 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4386 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4388 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4389 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4390 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4391 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4392 if(board[BOARD_HEIGHT-1][k] == bKing)
4393 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4394 if(gameInfo.variant == VariantTwoKings) {
4395 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4396 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4397 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4400 r = boards[moveNum][CASTLING][0] = initialRights[0];
4401 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4402 r = boards[moveNum][CASTLING][1] = initialRights[1];
4403 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4404 r = boards[moveNum][CASTLING][3] = initialRights[3];
4405 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4406 r = boards[moveNum][CASTLING][4] = initialRights[4];
4407 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4408 /* wildcastle kludge: always assume King has rights */
4409 r = boards[moveNum][CASTLING][2] = initialRights[2];
4410 r = boards[moveNum][CASTLING][5] = initialRights[5];
4412 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4413 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4416 if (ics_getting_history == H_GOT_REQ_HEADER ||
4417 ics_getting_history == H_GOT_UNREQ_HEADER) {
4418 /* This was an initial position from a move list, not
4419 the current position */
4423 /* Update currentMove and known move number limits */
4424 newMove = newGame || moveNum > forwardMostMove;
4427 forwardMostMove = backwardMostMove = currentMove = moveNum;
4428 if (gameMode == IcsExamining && moveNum == 0) {
4429 /* Workaround for ICS limitation: we are not told the wild
4430 type when starting to examine a game. But if we ask for
4431 the move list, the move list header will tell us */
4432 ics_getting_history = H_REQUESTED;
4433 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4436 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4437 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4439 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4440 /* [HGM] applied this also to an engine that is silently watching */
4441 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4442 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4443 gameInfo.variant == currentlyInitializedVariant) {
4444 takeback = forwardMostMove - moveNum;
4445 for (i = 0; i < takeback; i++) {
4446 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4447 SendToProgram("undo\n", &first);
4452 forwardMostMove = moveNum;
4453 if (!pausing || currentMove > forwardMostMove)
4454 currentMove = forwardMostMove;
4456 /* New part of history that is not contiguous with old part */
4457 if (pausing && gameMode == IcsExamining) {
4458 pauseExamInvalid = TRUE;
4459 forwardMostMove = pauseExamForwardMostMove;
4462 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4464 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4465 // [HGM] when we will receive the move list we now request, it will be
4466 // fed to the engine from the first move on. So if the engine is not
4467 // in the initial position now, bring it there.
4468 InitChessProgram(&first, 0);
4471 ics_getting_history = H_REQUESTED;
4472 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4475 forwardMostMove = backwardMostMove = currentMove = moveNum;
4478 /* Update the clocks */
4479 if (strchr(elapsed_time, '.')) {
4481 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4482 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4484 /* Time is in seconds */
4485 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4486 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4491 if (appData.zippyPlay && newGame &&
4492 gameMode != IcsObserving && gameMode != IcsIdle &&
4493 gameMode != IcsExamining)
4494 ZippyFirstBoard(moveNum, basetime, increment);
4497 /* Put the move on the move list, first converting
4498 to canonical algebraic form. */
4500 if (appData.debugMode) {
4501 if (appData.debugMode) { int f = forwardMostMove;
4502 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4503 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4504 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4506 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4507 fprintf(debugFP, "moveNum = %d\n", moveNum);
4508 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4509 setbuf(debugFP, NULL);
4511 if (moveNum <= backwardMostMove) {
4512 /* We don't know what the board looked like before
4514 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4515 strcat(parseList[moveNum - 1], " ");
4516 strcat(parseList[moveNum - 1], elapsed_time);
4517 moveList[moveNum - 1][0] = NULLCHAR;
4518 } else if (strcmp(move_str, "none") == 0) {
4519 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4520 /* Again, we don't know what the board looked like;
4521 this is really the start of the game. */
4522 parseList[moveNum - 1][0] = NULLCHAR;
4523 moveList[moveNum - 1][0] = NULLCHAR;
4524 backwardMostMove = moveNum;
4525 startedFromSetupPosition = TRUE;
4526 fromX = fromY = toX = toY = -1;
4528 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4529 // So we parse the long-algebraic move string in stead of the SAN move
4530 int valid; char buf[MSG_SIZ], *prom;
4532 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4533 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4534 // str looks something like "Q/a1-a2"; kill the slash
4536 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4537 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4538 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4539 strcat(buf, prom); // long move lacks promo specification!
4540 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4541 if(appData.debugMode)
4542 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4543 safeStrCpy(move_str, buf, MSG_SIZ);
4545 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4546 &fromX, &fromY, &toX, &toY, &promoChar)
4547 || ParseOneMove(buf, moveNum - 1, &moveType,
4548 &fromX, &fromY, &toX, &toY, &promoChar);
4549 // end of long SAN patch
4551 (void) CoordsToAlgebraic(boards[moveNum - 1],
4552 PosFlags(moveNum - 1),
4553 fromY, fromX, toY, toX, promoChar,
4554 parseList[moveNum-1]);
4555 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4561 if(gameInfo.variant != VariantShogi)
4562 strcat(parseList[moveNum - 1], "+");
4565 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4566 strcat(parseList[moveNum - 1], "#");
4569 strcat(parseList[moveNum - 1], " ");
4570 strcat(parseList[moveNum - 1], elapsed_time);
4571 /* currentMoveString is set as a side-effect of ParseOneMove */
4572 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4573 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4574 strcat(moveList[moveNum - 1], "\n");
4576 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4577 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4578 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4579 ChessSquare old, new = boards[moveNum][k][j];
4580 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4581 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4582 if(old == new) continue;
4583 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4584 else if(new == WhiteWazir || new == BlackWazir) {
4585 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4586 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4587 else boards[moveNum][k][j] = old; // preserve type of Gold
4588 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4589 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4592 /* Move from ICS was illegal!? Punt. */
4593 if (appData.debugMode) {
4594 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4595 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4597 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4598 strcat(parseList[moveNum - 1], " ");
4599 strcat(parseList[moveNum - 1], elapsed_time);
4600 moveList[moveNum - 1][0] = NULLCHAR;
4601 fromX = fromY = toX = toY = -1;
4604 if (appData.debugMode) {
4605 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4606 setbuf(debugFP, NULL);
4610 /* Send move to chess program (BEFORE animating it). */
4611 if (appData.zippyPlay && !newGame && newMove &&
4612 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4614 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4615 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4616 if (moveList[moveNum - 1][0] == NULLCHAR) {
4617 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4619 DisplayError(str, 0);
4621 if (first.sendTime) {
4622 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4624 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4625 if (firstMove && !bookHit) {
4627 if (first.useColors) {
4628 SendToProgram(gameMode == IcsPlayingWhite ?
4630 "black\ngo\n", &first);
4632 SendToProgram("go\n", &first);
4634 first.maybeThinking = TRUE;
4637 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4638 if (moveList[moveNum - 1][0] == NULLCHAR) {
4639 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4640 DisplayError(str, 0);
4642 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4643 SendMoveToProgram(moveNum - 1, &first);
4650 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4651 /* If move comes from a remote source, animate it. If it
4652 isn't remote, it will have already been animated. */
4653 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4654 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4656 if (!pausing && appData.highlightLastMove) {
4657 SetHighlights(fromX, fromY, toX, toY);
4661 /* Start the clocks */
4662 whiteFlag = blackFlag = FALSE;
4663 appData.clockMode = !(basetime == 0 && increment == 0);
4665 ics_clock_paused = TRUE;
4667 } else if (ticking == 1) {
4668 ics_clock_paused = FALSE;
4670 if (gameMode == IcsIdle ||
4671 relation == RELATION_OBSERVING_STATIC ||
4672 relation == RELATION_EXAMINING ||
4674 DisplayBothClocks();
4678 /* Display opponents and material strengths */
4679 if (gameInfo.variant != VariantBughouse &&
4680 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4681 if (tinyLayout || smallLayout) {
4682 if(gameInfo.variant == VariantNormal)
4683 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4684 gameInfo.white, white_stren, gameInfo.black, black_stren,
4685 basetime, increment);
4687 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4688 gameInfo.white, white_stren, gameInfo.black, black_stren,
4689 basetime, increment, (int) gameInfo.variant);
4691 if(gameInfo.variant == VariantNormal)
4692 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4693 gameInfo.white, white_stren, gameInfo.black, black_stren,
4694 basetime, increment);
4696 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4697 gameInfo.white, white_stren, gameInfo.black, black_stren,
4698 basetime, increment, VariantName(gameInfo.variant));
4701 if (appData.debugMode) {
4702 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4707 /* Display the board */
4708 if (!pausing && !appData.noGUI) {
4710 if (appData.premove)
4712 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4713 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4714 ClearPremoveHighlights();
4716 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4717 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4718 DrawPosition(j, boards[currentMove]);
4720 DisplayMove(moveNum - 1);
4721 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4722 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4723 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4724 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4728 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4730 if(bookHit) { // [HGM] book: simulate book reply
4731 static char bookMove[MSG_SIZ]; // a bit generous?
4733 programStats.nodes = programStats.depth = programStats.time =
4734 programStats.score = programStats.got_only_move = 0;
4735 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4737 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4738 strcat(bookMove, bookHit);
4739 HandleMachineMove(bookMove, &first);
4748 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4749 ics_getting_history = H_REQUESTED;
4750 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4756 AnalysisPeriodicEvent(force)
4759 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4760 && !force) || !appData.periodicUpdates)
4763 /* Send . command to Crafty to collect stats */
4764 SendToProgram(".\n", &first);
4766 /* Don't send another until we get a response (this makes
4767 us stop sending to old Crafty's which don't understand
4768 the "." command (sending illegal cmds resets node count & time,
4769 which looks bad)) */
4770 programStats.ok_to_send = 0;
4773 void ics_update_width(new_width)
4776 ics_printf("set width %d\n", new_width);
4780 SendMoveToProgram(moveNum, cps)
4782 ChessProgramState *cps;
4786 if (cps->useUsermove) {
4787 SendToProgram("usermove ", cps);
4791 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4792 int len = space - parseList[moveNum];
4793 memcpy(buf, parseList[moveNum], len);
4795 buf[len] = NULLCHAR;
4797 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4799 SendToProgram(buf, cps);
4801 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4802 AlphaRank(moveList[moveNum], 4);
4803 SendToProgram(moveList[moveNum], cps);
4804 AlphaRank(moveList[moveNum], 4); // and back
4806 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4807 * the engine. It would be nice to have a better way to identify castle
4809 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4810 && cps->useOOCastle) {
4811 int fromX = moveList[moveNum][0] - AAA;
4812 int fromY = moveList[moveNum][1] - ONE;
4813 int toX = moveList[moveNum][2] - AAA;
4814 int toY = moveList[moveNum][3] - ONE;
4815 if((boards[moveNum][fromY][fromX] == WhiteKing
4816 && boards[moveNum][toY][toX] == WhiteRook)
4817 || (boards[moveNum][fromY][fromX] == BlackKing
4818 && boards[moveNum][toY][toX] == BlackRook)) {
4819 if(toX > fromX) SendToProgram("O-O\n", cps);
4820 else SendToProgram("O-O-O\n", cps);
4822 else SendToProgram(moveList[moveNum], cps);
4824 else SendToProgram(moveList[moveNum], cps);
4825 /* End of additions by Tord */
4828 /* [HGM] setting up the opening has brought engine in force mode! */
4829 /* Send 'go' if we are in a mode where machine should play. */
4830 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4831 (gameMode == TwoMachinesPlay ||
4833 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4835 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4836 SendToProgram("go\n", cps);
4837 if (appData.debugMode) {
4838 fprintf(debugFP, "(extra)\n");
4841 setboardSpoiledMachineBlack = 0;
4845 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4847 int fromX, fromY, toX, toY;
4850 char user_move[MSG_SIZ];
4854 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4855 (int)moveType, fromX, fromY, toX, toY);
4856 DisplayError(user_move + strlen("say "), 0);
4858 case WhiteKingSideCastle:
4859 case BlackKingSideCastle:
4860 case WhiteQueenSideCastleWild:
4861 case BlackQueenSideCastleWild:
4863 case WhiteHSideCastleFR:
4864 case BlackHSideCastleFR:
4866 snprintf(user_move, MSG_SIZ, "o-o\n");
4868 case WhiteQueenSideCastle:
4869 case BlackQueenSideCastle:
4870 case WhiteKingSideCastleWild:
4871 case BlackKingSideCastleWild:
4873 case WhiteASideCastleFR:
4874 case BlackASideCastleFR:
4876 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4878 case WhiteNonPromotion:
4879 case BlackNonPromotion:
4880 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4882 case WhitePromotion:
4883 case BlackPromotion:
4884 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4885 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4886 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4887 PieceToChar(WhiteFerz));
4888 else if(gameInfo.variant == VariantGreat)
4889 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4890 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4891 PieceToChar(WhiteMan));
4893 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4894 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4900 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4901 ToUpper(PieceToChar((ChessSquare) fromX)),
4902 AAA + toX, ONE + toY);
4904 case IllegalMove: /* could be a variant we don't quite understand */
4905 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4907 case WhiteCapturesEnPassant:
4908 case BlackCapturesEnPassant:
4909 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4910 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4913 SendToICS(user_move);
4914 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4915 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4920 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4921 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4922 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4923 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4924 DisplayError("You cannot do this while you are playing or observing", 0);
4927 if(gameMode != IcsExamining) { // is this ever not the case?
4928 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4930 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4931 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4932 } else { // on FICS we must first go to general examine mode
4933 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4935 if(gameInfo.variant != VariantNormal) {
4936 // try figure out wild number, as xboard names are not always valid on ICS
4937 for(i=1; i<=36; i++) {
4938 snprintf(buf, MSG_SIZ, "wild/%d", i);
4939 if(StringToVariant(buf) == gameInfo.variant) break;
4941 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4942 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4943 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4944 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4945 SendToICS(ics_prefix);
4947 if(startedFromSetupPosition || backwardMostMove != 0) {
4948 fen = PositionToFEN(backwardMostMove, NULL);
4949 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4950 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4952 } else { // FICS: everything has to set by separate bsetup commands
4953 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4954 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4956 if(!WhiteOnMove(backwardMostMove)) {
4957 SendToICS("bsetup tomove black\n");
4959 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4960 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4962 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4963 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4965 i = boards[backwardMostMove][EP_STATUS];
4966 if(i >= 0) { // set e.p.
4967 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4973 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4974 SendToICS("bsetup done\n"); // switch to normal examining.
4976 for(i = backwardMostMove; i<last; i++) {
4978 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4981 SendToICS(ics_prefix);
4982 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4986 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4991 if (rf == DROP_RANK) {
4992 sprintf(move, "%c@%c%c\n",
4993 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4995 if (promoChar == 'x' || promoChar == NULLCHAR) {
4996 sprintf(move, "%c%c%c%c\n",
4997 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4999 sprintf(move, "%c%c%c%c%c\n",
5000 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5006 ProcessICSInitScript(f)
5011 while (fgets(buf, MSG_SIZ, f)) {
5012 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5019 static int lastX, lastY, selectFlag, dragging;
5024 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5025 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5026 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5027 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5028 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5029 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5032 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5033 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5034 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5035 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5037 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5038 appData.testLegality && (promoSweep == king ||
5039 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5040 ChangeDragPiece(promoSweep);
5043 int PromoScroll(int x, int y)
5047 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5048 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5049 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5050 if(!step) return FALSE;
5051 lastX = x; lastY = y;
5052 if((promoSweep < BlackPawn) == flipView) step = -step;
5053 if(step > 0) selectFlag = 1;
5054 if(!selectFlag) Sweep(step);
5061 ChessSquare piece = boards[currentMove][toY][toX];
5064 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5065 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5066 if(!step) step = -1;
5067 } while(PieceToChar(pieceSweep) == '.');
5068 boards[currentMove][toY][toX] = pieceSweep;
5069 DrawPosition(FALSE, boards[currentMove]);
5070 boards[currentMove][toY][toX] = piece;
5072 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5074 AlphaRank(char *move, int n)
5076 // char *p = move, c; int x, y;
5078 if (appData.debugMode) {
5079 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5083 move[2]>='0' && move[2]<='9' &&
5084 move[3]>='a' && move[3]<='x' ) {
5086 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5087 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5089 if(move[0]>='0' && move[0]<='9' &&
5090 move[1]>='a' && move[1]<='x' &&
5091 move[2]>='0' && move[2]<='9' &&
5092 move[3]>='a' && move[3]<='x' ) {
5093 /* input move, Shogi -> normal */
5094 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5095 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5096 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5097 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5100 move[3]>='0' && move[3]<='9' &&
5101 move[2]>='a' && move[2]<='x' ) {
5103 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5104 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5107 move[0]>='a' && move[0]<='x' &&
5108 move[3]>='0' && move[3]<='9' &&
5109 move[2]>='a' && move[2]<='x' ) {
5110 /* output move, normal -> Shogi */
5111 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5112 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5113 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5114 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5115 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5117 if (appData.debugMode) {
5118 fprintf(debugFP, " out = '%s'\n", move);
5122 char yy_textstr[8000];
5124 /* Parser for moves from gnuchess, ICS, or user typein box */
5126 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5129 ChessMove *moveType;
5130 int *fromX, *fromY, *toX, *toY;
5133 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5135 switch (*moveType) {
5136 case WhitePromotion:
5137 case BlackPromotion:
5138 case WhiteNonPromotion:
5139 case BlackNonPromotion:
5141 case WhiteCapturesEnPassant:
5142 case BlackCapturesEnPassant:
5143 case WhiteKingSideCastle:
5144 case WhiteQueenSideCastle:
5145 case BlackKingSideCastle:
5146 case BlackQueenSideCastle:
5147 case WhiteKingSideCastleWild:
5148 case WhiteQueenSideCastleWild:
5149 case BlackKingSideCastleWild:
5150 case BlackQueenSideCastleWild:
5151 /* Code added by Tord: */
5152 case WhiteHSideCastleFR:
5153 case WhiteASideCastleFR:
5154 case BlackHSideCastleFR:
5155 case BlackASideCastleFR:
5156 /* End of code added by Tord */
5157 case IllegalMove: /* bug or odd chess variant */
5158 *fromX = currentMoveString[0] - AAA;
5159 *fromY = currentMoveString[1] - ONE;
5160 *toX = currentMoveString[2] - AAA;
5161 *toY = currentMoveString[3] - ONE;
5162 *promoChar = currentMoveString[4];
5163 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5164 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5165 if (appData.debugMode) {
5166 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5168 *fromX = *fromY = *toX = *toY = 0;
5171 if (appData.testLegality) {
5172 return (*moveType != IllegalMove);
5174 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5175 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5180 *fromX = *moveType == WhiteDrop ?
5181 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5182 (int) CharToPiece(ToLower(currentMoveString[0]));
5184 *toX = currentMoveString[2] - AAA;
5185 *toY = currentMoveString[3] - ONE;
5186 *promoChar = NULLCHAR;
5190 case ImpossibleMove:
5200 if (appData.debugMode) {
5201 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5204 *fromX = *fromY = *toX = *toY = 0;
5205 *promoChar = NULLCHAR;
5212 ParsePV(char *pv, Boolean storeComments)
5213 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5214 int fromX, fromY, toX, toY; char promoChar;
5219 endPV = forwardMostMove;
5221 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5222 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5223 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5224 if(appData.debugMode){
5225 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);
5227 if(!valid && nr == 0 &&
5228 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5229 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5230 // Hande case where played move is different from leading PV move
5231 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5232 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5233 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5234 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5235 endPV += 2; // if position different, keep this
5236 moveList[endPV-1][0] = fromX + AAA;
5237 moveList[endPV-1][1] = fromY + ONE;
5238 moveList[endPV-1][2] = toX + AAA;
5239 moveList[endPV-1][3] = toY + ONE;
5240 parseList[endPV-1][0] = NULLCHAR;
5241 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5244 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5245 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5246 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5247 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5248 valid++; // allow comments in PV
5252 if(endPV+1 > framePtr) break; // no space, truncate
5255 CopyBoard(boards[endPV], boards[endPV-1]);
5256 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5257 moveList[endPV-1][0] = fromX + AAA;
5258 moveList[endPV-1][1] = fromY + ONE;
5259 moveList[endPV-1][2] = toX + AAA;
5260 moveList[endPV-1][3] = toY + ONE;
5261 moveList[endPV-1][4] = promoChar;
5262 moveList[endPV-1][5] = NULLCHAR;
5263 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5265 CoordsToAlgebraic(boards[endPV - 1],
5266 PosFlags(endPV - 1),
5267 fromY, fromX, toY, toX, promoChar,
5268 parseList[endPV - 1]);
5270 parseList[endPV-1][0] = NULLCHAR;
5272 currentMove = endPV;
5273 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5274 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5275 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5276 DrawPosition(TRUE, boards[currentMove]);
5280 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5285 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5286 lastX = x; lastY = y;
5287 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5289 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5290 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5292 do{ while(buf[index] && buf[index] != '\n') index++;
5293 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5295 ParsePV(buf+startPV, FALSE);
5296 *start = startPV; *end = index-1;
5301 LoadPV(int x, int y)
5302 { // called on right mouse click to load PV
5303 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5304 lastX = x; lastY = y;
5305 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5312 if(endPV < 0) return;
5314 currentMove = forwardMostMove;
5315 ClearPremoveHighlights();
5316 DrawPosition(TRUE, boards[currentMove]);
5320 MovePV(int x, int y, int h)
5321 { // step through PV based on mouse coordinates (called on mouse move)
5322 int margin = h>>3, step = 0;
5324 // we must somehow check if right button is still down (might be released off board!)
5325 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5326 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5327 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5329 lastX = x; lastY = y;
5331 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5332 if(endPV < 0) return;
5333 if(y < margin) step = 1; else
5334 if(y > h - margin) step = -1;
5335 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5336 currentMove += step;
5337 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5338 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5339 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5340 DrawPosition(FALSE, boards[currentMove]);
5344 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5345 // All positions will have equal probability, but the current method will not provide a unique
5346 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5352 int piecesLeft[(int)BlackPawn];
5353 int seed, nrOfShuffles;
5355 void GetPositionNumber()
5356 { // sets global variable seed
5359 seed = appData.defaultFrcPosition;
5360 if(seed < 0) { // randomize based on time for negative FRC position numbers
5361 for(i=0; i<50; i++) seed += random();
5362 seed = random() ^ random() >> 8 ^ random() << 8;
5363 if(seed<0) seed = -seed;
5367 int put(Board board, int pieceType, int rank, int n, int shade)
5368 // put the piece on the (n-1)-th empty squares of the given shade
5372 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5373 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5374 board[rank][i] = (ChessSquare) pieceType;
5375 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5377 piecesLeft[pieceType]--;
5385 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5386 // calculate where the next piece goes, (any empty square), and put it there
5390 i = seed % squaresLeft[shade];
5391 nrOfShuffles *= squaresLeft[shade];
5392 seed /= squaresLeft[shade];
5393 put(board, pieceType, rank, i, shade);
5396 void AddTwoPieces(Board board, int pieceType, int rank)
5397 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5399 int i, n=squaresLeft[ANY], j=n-1, k;
5401 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5402 i = seed % k; // pick one
5405 while(i >= j) i -= j--;
5406 j = n - 1 - j; i += j;
5407 put(board, pieceType, rank, j, ANY);
5408 put(board, pieceType, rank, i, ANY);
5411 void SetUpShuffle(Board board, int number)
5415 GetPositionNumber(); nrOfShuffles = 1;
5417 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5418 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5419 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5421 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5423 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5424 p = (int) board[0][i];
5425 if(p < (int) BlackPawn) piecesLeft[p] ++;
5426 board[0][i] = EmptySquare;
5429 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5430 // shuffles restricted to allow normal castling put KRR first
5431 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5432 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5433 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5434 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5435 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5436 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5437 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5438 put(board, WhiteRook, 0, 0, ANY);
5439 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5442 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5443 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5444 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5445 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5446 while(piecesLeft[p] >= 2) {
5447 AddOnePiece(board, p, 0, LITE);
5448 AddOnePiece(board, p, 0, DARK);
5450 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5453 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5454 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5455 // but we leave King and Rooks for last, to possibly obey FRC restriction
5456 if(p == (int)WhiteRook) continue;
5457 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5458 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5461 // now everything is placed, except perhaps King (Unicorn) and Rooks
5463 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5464 // Last King gets castling rights
5465 while(piecesLeft[(int)WhiteUnicorn]) {
5466 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5467 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5470 while(piecesLeft[(int)WhiteKing]) {
5471 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5472 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5477 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5478 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5481 // Only Rooks can be left; simply place them all
5482 while(piecesLeft[(int)WhiteRook]) {
5483 i = put(board, WhiteRook, 0, 0, ANY);
5484 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5487 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5489 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5492 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5493 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5496 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5499 int SetCharTable( char *table, const char * map )
5500 /* [HGM] moved here from winboard.c because of its general usefulness */
5501 /* Basically a safe strcpy that uses the last character as King */
5503 int result = FALSE; int NrPieces;
5505 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5506 && NrPieces >= 12 && !(NrPieces&1)) {
5507 int i; /* [HGM] Accept even length from 12 to 34 */
5509 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5510 for( i=0; i<NrPieces/2-1; i++ ) {
5512 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5514 table[(int) WhiteKing] = map[NrPieces/2-1];
5515 table[(int) BlackKing] = map[NrPieces-1];
5523 void Prelude(Board board)
5524 { // [HGM] superchess: random selection of exo-pieces
5525 int i, j, k; ChessSquare p;
5526 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5528 GetPositionNumber(); // use FRC position number
5530 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5531 SetCharTable(pieceToChar, appData.pieceToCharTable);
5532 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5533 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5536 j = seed%4; seed /= 4;
5537 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5538 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5539 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5540 j = seed%3 + (seed%3 >= j); seed /= 3;
5541 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5542 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5543 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5544 j = seed%3; seed /= 3;
5545 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5546 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5547 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5548 j = seed%2 + (seed%2 >= j); seed /= 2;
5549 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5550 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5551 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5552 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5553 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5554 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5555 put(board, exoPieces[0], 0, 0, ANY);
5556 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5560 InitPosition(redraw)
5563 ChessSquare (* pieces)[BOARD_FILES];
5564 int i, j, pawnRow, overrule,
5565 oldx = gameInfo.boardWidth,
5566 oldy = gameInfo.boardHeight,
5567 oldh = gameInfo.holdingsWidth;
5570 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5572 /* [AS] Initialize pv info list [HGM] and game status */
5574 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5575 pvInfoList[i].depth = 0;
5576 boards[i][EP_STATUS] = EP_NONE;
5577 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5580 initialRulePlies = 0; /* 50-move counter start */
5582 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5583 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5587 /* [HGM] logic here is completely changed. In stead of full positions */
5588 /* the initialized data only consist of the two backranks. The switch */
5589 /* selects which one we will use, which is than copied to the Board */
5590 /* initialPosition, which for the rest is initialized by Pawns and */
5591 /* empty squares. This initial position is then copied to boards[0], */
5592 /* possibly after shuffling, so that it remains available. */
5594 gameInfo.holdingsWidth = 0; /* default board sizes */
5595 gameInfo.boardWidth = 8;
5596 gameInfo.boardHeight = 8;
5597 gameInfo.holdingsSize = 0;
5598 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5599 for(i=0; i<BOARD_FILES-2; i++)
5600 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5601 initialPosition[EP_STATUS] = EP_NONE;
5602 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5603 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5604 SetCharTable(pieceNickName, appData.pieceNickNames);
5605 else SetCharTable(pieceNickName, "............");
5608 switch (gameInfo.variant) {
5609 case VariantFischeRandom:
5610 shuffleOpenings = TRUE;
5613 case VariantShatranj:
5614 pieces = ShatranjArray;
5615 nrCastlingRights = 0;
5616 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5619 pieces = makrukArray;
5620 nrCastlingRights = 0;
5621 startedFromSetupPosition = TRUE;
5622 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5624 case VariantTwoKings:
5625 pieces = twoKingsArray;
5627 case VariantCapaRandom:
5628 shuffleOpenings = TRUE;
5629 case VariantCapablanca:
5630 pieces = CapablancaArray;
5631 gameInfo.boardWidth = 10;
5632 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5635 pieces = GothicArray;
5636 gameInfo.boardWidth = 10;
5637 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5640 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5641 gameInfo.holdingsSize = 7;
5644 pieces = JanusArray;
5645 gameInfo.boardWidth = 10;
5646 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5647 nrCastlingRights = 6;
5648 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5649 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5650 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5651 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5652 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5653 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5656 pieces = FalconArray;
5657 gameInfo.boardWidth = 10;
5658 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5660 case VariantXiangqi:
5661 pieces = XiangqiArray;
5662 gameInfo.boardWidth = 9;
5663 gameInfo.boardHeight = 10;
5664 nrCastlingRights = 0;
5665 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5668 pieces = ShogiArray;
5669 gameInfo.boardWidth = 9;
5670 gameInfo.boardHeight = 9;
5671 gameInfo.holdingsSize = 7;
5672 nrCastlingRights = 0;
5673 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5675 case VariantCourier:
5676 pieces = CourierArray;
5677 gameInfo.boardWidth = 12;
5678 nrCastlingRights = 0;
5679 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5681 case VariantKnightmate:
5682 pieces = KnightmateArray;
5683 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5685 case VariantSpartan:
5686 pieces = SpartanArray;
5687 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5690 pieces = fairyArray;
5691 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5694 pieces = GreatArray;
5695 gameInfo.boardWidth = 10;
5696 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5697 gameInfo.holdingsSize = 8;
5701 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5702 gameInfo.holdingsSize = 8;
5703 startedFromSetupPosition = TRUE;
5705 case VariantCrazyhouse:
5706 case VariantBughouse:
5708 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5709 gameInfo.holdingsSize = 5;
5711 case VariantWildCastle:
5713 /* !!?shuffle with kings guaranteed to be on d or e file */
5714 shuffleOpenings = 1;
5716 case VariantNoCastle:
5718 nrCastlingRights = 0;
5719 /* !!?unconstrained back-rank shuffle */
5720 shuffleOpenings = 1;
5725 if(appData.NrFiles >= 0) {
5726 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5727 gameInfo.boardWidth = appData.NrFiles;
5729 if(appData.NrRanks >= 0) {
5730 gameInfo.boardHeight = appData.NrRanks;
5732 if(appData.holdingsSize >= 0) {
5733 i = appData.holdingsSize;
5734 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5735 gameInfo.holdingsSize = i;
5737 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5738 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5739 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5741 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5742 if(pawnRow < 1) pawnRow = 1;
5743 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5745 /* User pieceToChar list overrules defaults */
5746 if(appData.pieceToCharTable != NULL)
5747 SetCharTable(pieceToChar, appData.pieceToCharTable);
5749 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5751 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5752 s = (ChessSquare) 0; /* account holding counts in guard band */
5753 for( i=0; i<BOARD_HEIGHT; i++ )
5754 initialPosition[i][j] = s;
5756 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5757 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5758 initialPosition[pawnRow][j] = WhitePawn;
5759 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5760 if(gameInfo.variant == VariantXiangqi) {
5762 initialPosition[pawnRow][j] =
5763 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5764 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5765 initialPosition[2][j] = WhiteCannon;
5766 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5770 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5772 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5775 initialPosition[1][j] = WhiteBishop;
5776 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5778 initialPosition[1][j] = WhiteRook;
5779 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5782 if( nrCastlingRights == -1) {
5783 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5784 /* This sets default castling rights from none to normal corners */
5785 /* Variants with other castling rights must set them themselves above */
5786 nrCastlingRights = 6;
5788 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5789 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5790 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5791 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5792 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5793 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5796 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5797 if(gameInfo.variant == VariantGreat) { // promotion commoners
5798 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5799 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5800 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5801 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5803 if( gameInfo.variant == VariantSChess ) {
5804 initialPosition[1][0] = BlackMarshall;
5805 initialPosition[2][0] = BlackAngel;
5806 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5807 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5808 initialPosition[1][1] = initialPosition[2][1] =
5809 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5811 if (appData.debugMode) {
5812 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5814 if(shuffleOpenings) {
5815 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5816 startedFromSetupPosition = TRUE;
5818 if(startedFromPositionFile) {
5819 /* [HGM] loadPos: use PositionFile for every new game */
5820 CopyBoard(initialPosition, filePosition);
5821 for(i=0; i<nrCastlingRights; i++)
5822 initialRights[i] = filePosition[CASTLING][i];
5823 startedFromSetupPosition = TRUE;
5826 CopyBoard(boards[0], initialPosition);
5828 if(oldx != gameInfo.boardWidth ||
5829 oldy != gameInfo.boardHeight ||
5830 oldv != gameInfo.variant ||
5831 oldh != gameInfo.holdingsWidth
5833 InitDrawingSizes(-2 ,0);
5835 oldv = gameInfo.variant;
5837 DrawPosition(TRUE, boards[currentMove]);
5841 SendBoard(cps, moveNum)
5842 ChessProgramState *cps;
5845 char message[MSG_SIZ];
5847 if (cps->useSetboard) {
5848 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5849 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5850 SendToProgram(message, cps);
5856 /* Kludge to set black to move, avoiding the troublesome and now
5857 * deprecated "black" command.
5859 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5860 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5862 SendToProgram("edit\n", cps);
5863 SendToProgram("#\n", cps);
5864 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5865 bp = &boards[moveNum][i][BOARD_LEFT];
5866 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5867 if ((int) *bp < (int) BlackPawn) {
5868 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5870 if(message[0] == '+' || message[0] == '~') {
5871 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5872 PieceToChar((ChessSquare)(DEMOTED *bp)),
5875 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5876 message[1] = BOARD_RGHT - 1 - j + '1';
5877 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5879 SendToProgram(message, cps);
5884 SendToProgram("c\n", cps);
5885 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5886 bp = &boards[moveNum][i][BOARD_LEFT];
5887 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5888 if (((int) *bp != (int) EmptySquare)
5889 && ((int) *bp >= (int) BlackPawn)) {
5890 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5892 if(message[0] == '+' || message[0] == '~') {
5893 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5894 PieceToChar((ChessSquare)(DEMOTED *bp)),
5897 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5898 message[1] = BOARD_RGHT - 1 - j + '1';
5899 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5901 SendToProgram(message, cps);
5906 SendToProgram(".\n", cps);
5908 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5912 DefaultPromoChoice(int white)
5915 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5916 result = WhiteFerz; // no choice
5917 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5918 result= WhiteKing; // in Suicide Q is the last thing we want
5919 else if(gameInfo.variant == VariantSpartan)
5920 result = white ? WhiteQueen : WhiteAngel;
5921 else result = WhiteQueen;
5922 if(!white) result = WHITE_TO_BLACK result;
5926 static int autoQueen; // [HGM] oneclick
5929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5931 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5932 /* [HGM] add Shogi promotions */
5933 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5938 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5939 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5941 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5942 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5945 piece = boards[currentMove][fromY][fromX];
5946 if(gameInfo.variant == VariantShogi) {
5947 promotionZoneSize = BOARD_HEIGHT/3;
5948 highestPromotingPiece = (int)WhiteFerz;
5949 } else if(gameInfo.variant == VariantMakruk) {
5950 promotionZoneSize = 3;
5953 // Treat Lance as Pawn when it is not representing Amazon
5954 if(gameInfo.variant != VariantSuper) {
5955 if(piece == WhiteLance) piece = WhitePawn; else
5956 if(piece == BlackLance) piece = BlackPawn;
5959 // next weed out all moves that do not touch the promotion zone at all
5960 if((int)piece >= BlackPawn) {
5961 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5963 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5965 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5966 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5969 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5971 // weed out mandatory Shogi promotions
5972 if(gameInfo.variant == VariantShogi) {
5973 if(piece >= BlackPawn) {
5974 if(toY == 0 && piece == BlackPawn ||
5975 toY == 0 && piece == BlackQueen ||
5976 toY <= 1 && piece == BlackKnight) {
5981 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5982 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5983 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5990 // weed out obviously illegal Pawn moves
5991 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5992 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5993 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5994 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5995 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5996 // note we are not allowed to test for valid (non-)capture, due to premove
5999 // we either have a choice what to promote to, or (in Shogi) whether to promote
6000 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6001 *promoChoice = PieceToChar(BlackFerz); // no choice
6004 // no sense asking what we must promote to if it is going to explode...
6005 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6006 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6009 // give caller the default choice even if we will not make it
6010 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6011 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6012 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6013 && gameInfo.variant != VariantShogi
6014 && gameInfo.variant != VariantSuper) return FALSE;
6015 if(autoQueen) return FALSE; // predetermined
6017 // suppress promotion popup on illegal moves that are not premoves
6018 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6019 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6020 if(appData.testLegality && !premove) {
6021 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6022 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6023 if(moveType != WhitePromotion && moveType != BlackPromotion)
6031 InPalace(row, column)
6033 { /* [HGM] for Xiangqi */
6034 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6035 column < (BOARD_WIDTH + 4)/2 &&
6036 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6041 PieceForSquare (x, y)
6045 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6048 return boards[currentMove][y][x];
6052 OKToStartUserMove(x, y)
6055 ChessSquare from_piece;
6058 if (matchMode) return FALSE;
6059 if (gameMode == EditPosition) return TRUE;
6061 if (x >= 0 && y >= 0)
6062 from_piece = boards[currentMove][y][x];
6064 from_piece = EmptySquare;
6066 if (from_piece == EmptySquare) return FALSE;
6068 white_piece = (int)from_piece >= (int)WhitePawn &&
6069 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6072 case PlayFromGameFile:
6074 case TwoMachinesPlay:
6082 case MachinePlaysWhite:
6083 case IcsPlayingBlack:
6084 if (appData.zippyPlay) return FALSE;
6086 DisplayMoveError(_("You are playing Black"));
6091 case MachinePlaysBlack:
6092 case IcsPlayingWhite:
6093 if (appData.zippyPlay) return FALSE;
6095 DisplayMoveError(_("You are playing White"));
6101 if (!white_piece && WhiteOnMove(currentMove)) {
6102 DisplayMoveError(_("It is White's turn"));
6105 if (white_piece && !WhiteOnMove(currentMove)) {
6106 DisplayMoveError(_("It is Black's turn"));
6109 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6110 /* Editing correspondence game history */
6111 /* Could disallow this or prompt for confirmation */
6116 case BeginningOfGame:
6117 if (appData.icsActive) return FALSE;
6118 if (!appData.noChessProgram) {
6120 DisplayMoveError(_("You are playing White"));
6127 if (!white_piece && WhiteOnMove(currentMove)) {
6128 DisplayMoveError(_("It is White's turn"));
6131 if (white_piece && !WhiteOnMove(currentMove)) {
6132 DisplayMoveError(_("It is Black's turn"));
6141 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6142 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6143 && gameMode != AnalyzeFile && gameMode != Training) {
6144 DisplayMoveError(_("Displayed position is not current"));
6151 OnlyMove(int *x, int *y, Boolean captures) {
6152 DisambiguateClosure cl;
6153 if (appData.zippyPlay) return FALSE;
6155 case MachinePlaysBlack:
6156 case IcsPlayingWhite:
6157 case BeginningOfGame:
6158 if(!WhiteOnMove(currentMove)) return FALSE;
6160 case MachinePlaysWhite:
6161 case IcsPlayingBlack:
6162 if(WhiteOnMove(currentMove)) return FALSE;
6169 cl.pieceIn = EmptySquare;
6174 cl.promoCharIn = NULLCHAR;
6175 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6176 if( cl.kind == NormalMove ||
6177 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6178 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6179 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6186 if(cl.kind != ImpossibleMove) return FALSE;
6187 cl.pieceIn = EmptySquare;
6192 cl.promoCharIn = NULLCHAR;
6193 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6194 if( cl.kind == NormalMove ||
6195 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6196 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6197 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6202 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6208 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6209 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6210 int lastLoadGameUseList = FALSE;
6211 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6212 ChessMove lastLoadGameStart = EndOfFile;
6215 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6216 int fromX, fromY, toX, toY;
6220 ChessSquare pdown, pup;
6222 /* Check if the user is playing in turn. This is complicated because we
6223 let the user "pick up" a piece before it is his turn. So the piece he
6224 tried to pick up may have been captured by the time he puts it down!
6225 Therefore we use the color the user is supposed to be playing in this
6226 test, not the color of the piece that is currently on the starting
6227 square---except in EditGame mode, where the user is playing both
6228 sides; fortunately there the capture race can't happen. (It can
6229 now happen in IcsExamining mode, but that's just too bad. The user
6230 will get a somewhat confusing message in that case.)
6234 case PlayFromGameFile:
6236 case TwoMachinesPlay:
6240 /* We switched into a game mode where moves are not accepted,
6241 perhaps while the mouse button was down. */
6244 case MachinePlaysWhite:
6245 /* User is moving for Black */
6246 if (WhiteOnMove(currentMove)) {
6247 DisplayMoveError(_("It is White's turn"));
6252 case MachinePlaysBlack:
6253 /* User is moving for White */
6254 if (!WhiteOnMove(currentMove)) {
6255 DisplayMoveError(_("It is Black's turn"));
6262 case BeginningOfGame:
6265 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6266 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6267 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6268 /* User is moving for Black */
6269 if (WhiteOnMove(currentMove)) {
6270 DisplayMoveError(_("It is White's turn"));
6274 /* User is moving for White */
6275 if (!WhiteOnMove(currentMove)) {
6276 DisplayMoveError(_("It is Black's turn"));
6282 case IcsPlayingBlack:
6283 /* User is moving for Black */
6284 if (WhiteOnMove(currentMove)) {
6285 if (!appData.premove) {
6286 DisplayMoveError(_("It is White's turn"));
6287 } else if (toX >= 0 && toY >= 0) {
6290 premoveFromX = fromX;
6291 premoveFromY = fromY;
6292 premovePromoChar = promoChar;
6294 if (appData.debugMode)
6295 fprintf(debugFP, "Got premove: fromX %d,"
6296 "fromY %d, toX %d, toY %d\n",
6297 fromX, fromY, toX, toY);
6303 case IcsPlayingWhite:
6304 /* User is moving for White */
6305 if (!WhiteOnMove(currentMove)) {
6306 if (!appData.premove) {
6307 DisplayMoveError(_("It is Black's turn"));
6308 } else if (toX >= 0 && toY >= 0) {
6311 premoveFromX = fromX;
6312 premoveFromY = fromY;
6313 premovePromoChar = promoChar;
6315 if (appData.debugMode)
6316 fprintf(debugFP, "Got premove: fromX %d,"
6317 "fromY %d, toX %d, toY %d\n",
6318 fromX, fromY, toX, toY);
6328 /* EditPosition, empty square, or different color piece;
6329 click-click move is possible */
6330 if (toX == -2 || toY == -2) {
6331 boards[0][fromY][fromX] = EmptySquare;
6332 DrawPosition(FALSE, boards[currentMove]);
6334 } else if (toX >= 0 && toY >= 0) {
6335 boards[0][toY][toX] = boards[0][fromY][fromX];
6336 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6337 if(boards[0][fromY][0] != EmptySquare) {
6338 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6339 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6342 if(fromX == BOARD_RGHT+1) {
6343 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6344 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6345 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6348 boards[0][fromY][fromX] = EmptySquare;
6349 DrawPosition(FALSE, boards[currentMove]);
6355 if(toX < 0 || toY < 0) return;
6356 pdown = boards[currentMove][fromY][fromX];
6357 pup = boards[currentMove][toY][toX];
6359 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6360 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6361 if( pup != EmptySquare ) return;
6362 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6363 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6364 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6365 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6366 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6367 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6368 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6372 /* [HGM] always test for legality, to get promotion info */
6373 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6374 fromY, fromX, toY, toX, promoChar);
6375 /* [HGM] but possibly ignore an IllegalMove result */
6376 if (appData.testLegality) {
6377 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6378 DisplayMoveError(_("Illegal move"));
6383 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6386 /* Common tail of UserMoveEvent and DropMenuEvent */
6388 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6390 int fromX, fromY, toX, toY;
6391 /*char*/int promoChar;
6395 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6396 // [HGM] superchess: suppress promotions to non-available piece
6397 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6398 if(WhiteOnMove(currentMove)) {
6399 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6401 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6405 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6406 move type in caller when we know the move is a legal promotion */
6407 if(moveType == NormalMove && promoChar)
6408 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6410 /* [HGM] <popupFix> The following if has been moved here from
6411 UserMoveEvent(). Because it seemed to belong here (why not allow
6412 piece drops in training games?), and because it can only be
6413 performed after it is known to what we promote. */
6414 if (gameMode == Training) {
6415 /* compare the move played on the board to the next move in the
6416 * game. If they match, display the move and the opponent's response.
6417 * If they don't match, display an error message.
6421 CopyBoard(testBoard, boards[currentMove]);
6422 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6424 if (CompareBoards(testBoard, boards[currentMove+1])) {
6425 ForwardInner(currentMove+1);
6427 /* Autoplay the opponent's response.
6428 * if appData.animate was TRUE when Training mode was entered,
6429 * the response will be animated.
6431 saveAnimate = appData.animate;
6432 appData.animate = animateTraining;
6433 ForwardInner(currentMove+1);
6434 appData.animate = saveAnimate;
6436 /* check for the end of the game */
6437 if (currentMove >= forwardMostMove) {
6438 gameMode = PlayFromGameFile;
6440 SetTrainingModeOff();
6441 DisplayInformation(_("End of game"));
6444 DisplayError(_("Incorrect move"), 0);
6449 /* Ok, now we know that the move is good, so we can kill
6450 the previous line in Analysis Mode */
6451 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6452 && currentMove < forwardMostMove) {
6453 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6454 else forwardMostMove = currentMove;
6457 /* If we need the chess program but it's dead, restart it */
6458 ResurrectChessProgram();
6460 /* A user move restarts a paused game*/
6464 thinkOutput[0] = NULLCHAR;
6466 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6468 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6469 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6473 if (gameMode == BeginningOfGame) {
6474 if (appData.noChessProgram) {
6475 gameMode = EditGame;
6479 gameMode = MachinePlaysBlack;
6482 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6484 if (first.sendName) {
6485 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6486 SendToProgram(buf, &first);
6493 /* Relay move to ICS or chess engine */
6494 if (appData.icsActive) {
6495 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6496 gameMode == IcsExamining) {
6497 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6498 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6500 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6502 // also send plain move, in case ICS does not understand atomic claims
6503 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6507 if (first.sendTime && (gameMode == BeginningOfGame ||
6508 gameMode == MachinePlaysWhite ||
6509 gameMode == MachinePlaysBlack)) {
6510 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6512 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6513 // [HGM] book: if program might be playing, let it use book
6514 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6515 first.maybeThinking = TRUE;
6516 } else SendMoveToProgram(forwardMostMove-1, &first);
6517 if (currentMove == cmailOldMove + 1) {
6518 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6522 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6526 if(appData.testLegality)
6527 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6533 if (WhiteOnMove(currentMove)) {
6534 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6536 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6540 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6545 case MachinePlaysBlack:
6546 case MachinePlaysWhite:
6547 /* disable certain menu options while machine is thinking */
6548 SetMachineThinkingEnables();
6555 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6556 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6558 if(bookHit) { // [HGM] book: simulate book reply
6559 static char bookMove[MSG_SIZ]; // a bit generous?
6561 programStats.nodes = programStats.depth = programStats.time =
6562 programStats.score = programStats.got_only_move = 0;
6563 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6565 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6566 strcat(bookMove, bookHit);
6567 HandleMachineMove(bookMove, &first);
6573 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6580 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6581 Markers *m = (Markers *) closure;
6582 if(rf == fromY && ff == fromX)
6583 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6584 || kind == WhiteCapturesEnPassant
6585 || kind == BlackCapturesEnPassant);
6586 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6590 MarkTargetSquares(int clear)
6593 if(!appData.markers || !appData.highlightDragging ||
6594 !appData.testLegality || gameMode == EditPosition) return;
6596 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6599 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6600 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6601 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6603 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6606 DrawPosition(TRUE, NULL);
6610 Explode(Board board, int fromX, int fromY, int toX, int toY)
6612 if(gameInfo.variant == VariantAtomic &&
6613 (board[toY][toX] != EmptySquare || // capture?
6614 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6615 board[fromY][fromX] == BlackPawn )
6617 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6623 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6625 int CanPromote(ChessSquare piece, int y)
6627 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6628 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6629 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6630 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6631 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6632 gameInfo.variant == VariantMakruk) return FALSE;
6633 return (piece == BlackPawn && y == 1 ||
6634 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6635 piece == BlackLance && y == 1 ||
6636 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6639 void LeftClick(ClickType clickType, int xPix, int yPix)
6642 Boolean saveAnimate;
6643 static int second = 0, promotionChoice = 0, clearFlag = 0;
6644 char promoChoice = NULLCHAR;
6647 if(appData.seekGraph && appData.icsActive && loggedOn &&
6648 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6649 SeekGraphClick(clickType, xPix, yPix, 0);
6653 if (clickType == Press) ErrorPopDown();
6654 MarkTargetSquares(1);
6656 x = EventToSquare(xPix, BOARD_WIDTH);
6657 y = EventToSquare(yPix, BOARD_HEIGHT);
6658 if (!flipView && y >= 0) {
6659 y = BOARD_HEIGHT - 1 - y;
6661 if (flipView && x >= 0) {
6662 x = BOARD_WIDTH - 1 - x;
6665 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6666 defaultPromoChoice = promoSweep;
6667 promoSweep = EmptySquare; // terminate sweep
6668 promoDefaultAltered = TRUE;
6669 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6672 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6673 if(clickType == Release) return; // ignore upclick of click-click destination
6674 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6675 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6676 if(gameInfo.holdingsWidth &&
6677 (WhiteOnMove(currentMove)
6678 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6679 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6680 // click in right holdings, for determining promotion piece
6681 ChessSquare p = boards[currentMove][y][x];
6682 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6683 if(p != EmptySquare) {
6684 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6689 DrawPosition(FALSE, boards[currentMove]);
6693 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6694 if(clickType == Press
6695 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6696 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6697 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6700 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6701 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6703 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6704 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6705 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6706 defaultPromoChoice = DefaultPromoChoice(side);
6709 autoQueen = appData.alwaysPromoteToQueen;
6713 gatingPiece = EmptySquare;
6714 if (clickType != Press) {
6715 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6716 DragPieceEnd(xPix, yPix); dragging = 0;
6717 DrawPosition(FALSE, NULL);
6721 fromX = x; fromY = y;
6722 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6723 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6724 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6726 if (OKToStartUserMove(fromX, fromY)) {
6728 MarkTargetSquares(0);
6729 DragPieceBegin(xPix, yPix); dragging = 1;
6730 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6731 promoSweep = defaultPromoChoice;
6732 selectFlag = 0; lastX = xPix; lastY = yPix;
6733 Sweep(0); // Pawn that is going to promote: preview promotion piece
6734 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6736 if (appData.highlightDragging) {
6737 SetHighlights(fromX, fromY, -1, -1);
6739 } else fromX = fromY = -1;
6745 if (clickType == Press && gameMode != EditPosition) {
6750 // ignore off-board to clicks
6751 if(y < 0 || x < 0) return;
6753 /* Check if clicking again on the same color piece */
6754 fromP = boards[currentMove][fromY][fromX];
6755 toP = boards[currentMove][y][x];
6756 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6757 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6758 WhitePawn <= toP && toP <= WhiteKing &&
6759 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6760 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6761 (BlackPawn <= fromP && fromP <= BlackKing &&
6762 BlackPawn <= toP && toP <= BlackKing &&
6763 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6764 !(fromP == BlackKing && toP == BlackRook && frc))) {
6765 /* Clicked again on same color piece -- changed his mind */
6766 second = (x == fromX && y == fromY);
6767 promoDefaultAltered = FALSE;
6768 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6769 if (appData.highlightDragging) {
6770 SetHighlights(x, y, -1, -1);
6774 if (OKToStartUserMove(x, y)) {
6775 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6776 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6777 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6778 gatingPiece = boards[currentMove][fromY][fromX];
6779 else gatingPiece = EmptySquare;
6781 fromY = y; dragging = 1;
6782 MarkTargetSquares(0);
6783 DragPieceBegin(xPix, yPix);
6784 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6785 promoSweep = defaultPromoChoice;
6786 selectFlag = 0; lastX = xPix; lastY = yPix;
6787 Sweep(0); // Pawn that is going to promote: preview promotion piece
6791 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6794 // ignore clicks on holdings
6795 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6798 if (clickType == Release && x == fromX && y == fromY) {
6799 DragPieceEnd(xPix, yPix); dragging = 0;
6801 // a deferred attempt to click-click move an empty square on top of a piece
6802 boards[currentMove][y][x] = EmptySquare;
6804 DrawPosition(FALSE, boards[currentMove]);
6805 fromX = fromY = -1; clearFlag = 0;
6808 if (appData.animateDragging) {
6809 /* Undo animation damage if any */
6810 DrawPosition(FALSE, NULL);
6813 /* Second up/down in same square; just abort move */
6816 gatingPiece = EmptySquare;
6819 ClearPremoveHighlights();
6821 /* First upclick in same square; start click-click mode */
6822 SetHighlights(x, y, -1, -1);
6829 /* we now have a different from- and (possibly off-board) to-square */
6830 /* Completed move */
6833 saveAnimate = appData.animate;
6834 if (clickType == Press) {
6835 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6836 // must be Edit Position mode with empty-square selected
6837 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6838 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6841 /* Finish clickclick move */
6842 if (appData.animate || appData.highlightLastMove) {
6843 SetHighlights(fromX, fromY, toX, toY);
6848 /* Finish drag move */
6849 if (appData.highlightLastMove) {
6850 SetHighlights(fromX, fromY, toX, toY);
6854 DragPieceEnd(xPix, yPix); dragging = 0;
6855 /* Don't animate move and drag both */
6856 appData.animate = FALSE;
6859 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6860 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6861 ChessSquare piece = boards[currentMove][fromY][fromX];
6862 if(gameMode == EditPosition && piece != EmptySquare &&
6863 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6866 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6867 n = PieceToNumber(piece - (int)BlackPawn);
6868 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6869 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6870 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6872 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6873 n = PieceToNumber(piece);
6874 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6875 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6876 boards[currentMove][n][BOARD_WIDTH-2]++;
6878 boards[currentMove][fromY][fromX] = EmptySquare;
6882 DrawPosition(TRUE, boards[currentMove]);
6886 // off-board moves should not be highlighted
6887 if(x < 0 || y < 0) ClearHighlights();
6889 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6891 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6892 SetHighlights(fromX, fromY, toX, toY);
6893 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6894 // [HGM] super: promotion to captured piece selected from holdings
6895 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6896 promotionChoice = TRUE;
6897 // kludge follows to temporarily execute move on display, without promoting yet
6898 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6899 boards[currentMove][toY][toX] = p;
6900 DrawPosition(FALSE, boards[currentMove]);
6901 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6902 boards[currentMove][toY][toX] = q;
6903 DisplayMessage("Click in holdings to choose piece", "");
6908 int oldMove = currentMove;
6909 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6910 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6911 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6912 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6913 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6914 DrawPosition(TRUE, boards[currentMove]);
6917 appData.animate = saveAnimate;
6918 if (appData.animate || appData.animateDragging) {
6919 /* Undo animation damage if needed */
6920 DrawPosition(FALSE, NULL);
6924 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6925 { // front-end-free part taken out of PieceMenuPopup
6926 int whichMenu; int xSqr, ySqr;
6928 if(seekGraphUp) { // [HGM] seekgraph
6929 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6930 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6934 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6935 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6936 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6937 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6938 if(action == Press) {
6939 originalFlip = flipView;
6940 flipView = !flipView; // temporarily flip board to see game from partners perspective
6941 DrawPosition(TRUE, partnerBoard);
6942 DisplayMessage(partnerStatus, "");
6944 } else if(action == Release) {
6945 flipView = originalFlip;
6946 DrawPosition(TRUE, boards[currentMove]);
6952 xSqr = EventToSquare(x, BOARD_WIDTH);
6953 ySqr = EventToSquare(y, BOARD_HEIGHT);
6954 if (action == Release) {
6955 if(pieceSweep != EmptySquare) {
6956 EditPositionMenuEvent(pieceSweep, toX, toY);
6957 pieceSweep = EmptySquare;
6958 } else UnLoadPV(); // [HGM] pv
6960 if (action != Press) return -2; // return code to be ignored
6963 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6965 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6966 if (xSqr < 0 || ySqr < 0) return -1;
6967 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6968 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6969 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6970 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6974 if(!appData.icsEngineAnalyze) return -1;
6975 case IcsPlayingWhite:
6976 case IcsPlayingBlack:
6977 if(!appData.zippyPlay) goto noZip;
6980 case MachinePlaysWhite:
6981 case MachinePlaysBlack:
6982 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6983 if (!appData.dropMenu) {
6985 return 2; // flag front-end to grab mouse events
6987 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6988 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6991 if (xSqr < 0 || ySqr < 0) return -1;
6992 if (!appData.dropMenu || appData.testLegality &&
6993 gameInfo.variant != VariantBughouse &&
6994 gameInfo.variant != VariantCrazyhouse) return -1;
6995 whichMenu = 1; // drop menu
7001 if (((*fromX = xSqr) < 0) ||
7002 ((*fromY = ySqr) < 0)) {
7003 *fromX = *fromY = -1;
7007 *fromX = BOARD_WIDTH - 1 - *fromX;
7009 *fromY = BOARD_HEIGHT - 1 - *fromY;
7014 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7016 // char * hint = lastHint;
7017 FrontEndProgramStats stats;
7019 stats.which = cps == &first ? 0 : 1;
7020 stats.depth = cpstats->depth;
7021 stats.nodes = cpstats->nodes;
7022 stats.score = cpstats->score;
7023 stats.time = cpstats->time;
7024 stats.pv = cpstats->movelist;
7025 stats.hint = lastHint;
7026 stats.an_move_index = 0;
7027 stats.an_move_count = 0;
7029 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7030 stats.hint = cpstats->move_name;
7031 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7032 stats.an_move_count = cpstats->nr_moves;
7035 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
7037 SetProgramStats( &stats );
7040 #define MAXPLAYERS 500
7043 TourneyStandings(int display)
7045 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7046 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7047 char result, *p, *names[MAXPLAYERS];
7049 names[0] = p = strdup(appData.participants);
7050 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7052 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7054 while(result = appData.results[nr]) {
7055 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7056 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7057 wScore = bScore = 0;
7059 case '+': wScore = 2; break;
7060 case '-': bScore = 2; break;
7061 case '=': wScore = bScore = 1; break;
7063 case '*': return NULL; // tourney not finished
7071 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7072 for(w=0; w<nPlayers; w++) {
7074 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7075 ranking[w] = b; points[w] = bScore; score[b] = -2;
7077 p = malloc(nPlayers*34+1);
7078 for(w=0; w<nPlayers && w<display; w++)
7079 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7085 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7086 { // count all piece types
7088 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7089 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7090 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7093 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7094 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7095 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7096 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7097 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7098 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7103 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7105 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7106 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7108 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7109 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7110 if(myPawns == 2 && nMine == 3) // KPP
7111 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7112 if(myPawns == 1 && nMine == 2) // KP
7113 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7114 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7115 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7116 if(myPawns) return FALSE;
7117 if(pCnt[WhiteRook+side])
7118 return pCnt[BlackRook-side] ||
7119 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7120 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7121 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7122 if(pCnt[WhiteCannon+side]) {
7123 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7124 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7126 if(pCnt[WhiteKnight+side])
7127 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7132 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7134 VariantClass v = gameInfo.variant;
7136 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7137 if(v == VariantShatranj) return TRUE; // always winnable through baring
7138 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7139 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7141 if(v == VariantXiangqi) {
7142 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7144 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7145 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7146 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7147 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7148 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7149 if(stale) // we have at least one last-rank P plus perhaps C
7150 return majors // KPKX
7151 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7153 return pCnt[WhiteFerz+side] // KCAK
7154 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7155 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7156 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7158 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7159 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7161 if(nMine == 1) return FALSE; // bare King
7162 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
7163 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7164 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7165 // by now we have King + 1 piece (or multiple Bishops on the same color)
7166 if(pCnt[WhiteKnight+side])
7167 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7168 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7169 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7171 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7172 if(pCnt[WhiteAlfil+side])
7173 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7174 if(pCnt[WhiteWazir+side])
7175 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7182 Adjudicate(ChessProgramState *cps)
7183 { // [HGM] some adjudications useful with buggy engines
7184 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7185 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7186 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7187 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7188 int k, count = 0; static int bare = 1;
7189 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7190 Boolean canAdjudicate = !appData.icsActive;
7192 // most tests only when we understand the game, i.e. legality-checking on
7193 if( appData.testLegality )
7194 { /* [HGM] Some more adjudications for obstinate engines */
7195 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7196 static int moveCount = 6;
7198 char *reason = NULL;
7200 /* Count what is on board. */
7201 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7203 /* Some material-based adjudications that have to be made before stalemate test */
7204 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7205 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7206 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7207 if(canAdjudicate && appData.checkMates) {
7209 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7210 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7211 "Xboard adjudication: King destroyed", GE_XBOARD );
7216 /* Bare King in Shatranj (loses) or Losers (wins) */
7217 if( nrW == 1 || nrB == 1) {
7218 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7219 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7220 if(canAdjudicate && appData.checkMates) {
7222 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7223 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7224 "Xboard adjudication: Bare king", GE_XBOARD );
7228 if( gameInfo.variant == VariantShatranj && --bare < 0)
7230 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7231 if(canAdjudicate && appData.checkMates) {
7232 /* but only adjudicate if adjudication enabled */
7234 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7235 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7236 "Xboard adjudication: Bare king", GE_XBOARD );
7243 // don't wait for engine to announce game end if we can judge ourselves
7244 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7246 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7247 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7248 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7249 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7252 reason = "Xboard adjudication: 3rd check";
7253 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7263 reason = "Xboard adjudication: Stalemate";
7264 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7265 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7266 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7267 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7268 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7269 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7270 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7271 EP_CHECKMATE : EP_WINS);
7272 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7273 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7277 reason = "Xboard adjudication: Checkmate";
7278 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7282 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7284 result = GameIsDrawn; break;
7286 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7288 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7292 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7294 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7295 GameEnds( result, reason, GE_XBOARD );
7299 /* Next absolutely insufficient mating material. */
7300 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7301 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7302 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7304 /* always flag draws, for judging claims */
7305 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7307 if(canAdjudicate && appData.materialDraws) {
7308 /* but only adjudicate them if adjudication enabled */
7309 if(engineOpponent) {
7310 SendToProgram("force\n", engineOpponent); // suppress reply
7311 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7313 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7318 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7319 if(gameInfo.variant == VariantXiangqi ?
7320 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7322 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7323 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7324 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7325 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7327 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7328 { /* if the first 3 moves do not show a tactical win, declare draw */
7329 if(engineOpponent) {
7330 SendToProgram("force\n", engineOpponent); // suppress reply
7331 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7333 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7336 } else moveCount = 6;
7338 if (appData.debugMode) { int i;
7339 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7340 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7341 appData.drawRepeats);
7342 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7343 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7347 // Repetition draws and 50-move rule can be applied independently of legality testing
7349 /* Check for rep-draws */
7351 for(k = forwardMostMove-2;
7352 k>=backwardMostMove && k>=forwardMostMove-100 &&
7353 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7354 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7357 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7358 /* compare castling rights */
7359 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7360 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7361 rights++; /* King lost rights, while rook still had them */
7362 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7363 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7364 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7365 rights++; /* but at least one rook lost them */
7367 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7368 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7370 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7371 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7372 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7375 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7376 && appData.drawRepeats > 1) {
7377 /* adjudicate after user-specified nr of repeats */
7378 int result = GameIsDrawn;
7379 char *details = "XBoard adjudication: repetition draw";
7380 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7381 // [HGM] xiangqi: check for forbidden perpetuals
7382 int m, ourPerpetual = 1, hisPerpetual = 1;
7383 for(m=forwardMostMove; m>k; m-=2) {
7384 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7385 ourPerpetual = 0; // the current mover did not always check
7386 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7387 hisPerpetual = 0; // the opponent did not always check
7389 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7390 ourPerpetual, hisPerpetual);
7391 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7392 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7393 details = "Xboard adjudication: perpetual checking";
7395 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7396 break; // (or we would have caught him before). Abort repetition-checking loop.
7398 // Now check for perpetual chases
7399 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7400 hisPerpetual = PerpetualChase(k, forwardMostMove);
7401 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7402 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7403 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7404 details = "Xboard adjudication: perpetual chasing";
7406 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7407 break; // Abort repetition-checking loop.
7409 // if neither of us is checking or chasing all the time, or both are, it is draw
7411 if(engineOpponent) {
7412 SendToProgram("force\n", engineOpponent); // suppress reply
7413 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7415 GameEnds( result, details, GE_XBOARD );
7418 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7419 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7423 /* Now we test for 50-move draws. Determine ply count */
7424 count = forwardMostMove;
7425 /* look for last irreversble move */
7426 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7428 /* if we hit starting position, add initial plies */
7429 if( count == backwardMostMove )
7430 count -= initialRulePlies;
7431 count = forwardMostMove - count;
7432 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7433 // adjust reversible move counter for checks in Xiangqi
7434 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7435 if(i < backwardMostMove) i = backwardMostMove;
7436 while(i <= forwardMostMove) {
7437 lastCheck = inCheck; // check evasion does not count
7438 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7439 if(inCheck || lastCheck) count--; // check does not count
7444 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7445 /* this is used to judge if draw claims are legal */
7446 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7447 if(engineOpponent) {
7448 SendToProgram("force\n", engineOpponent); // suppress reply
7449 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7451 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7455 /* if draw offer is pending, treat it as a draw claim
7456 * when draw condition present, to allow engines a way to
7457 * claim draws before making their move to avoid a race
7458 * condition occurring after their move
7460 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7462 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7463 p = "Draw claim: 50-move rule";
7464 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7465 p = "Draw claim: 3-fold repetition";
7466 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7467 p = "Draw claim: insufficient mating material";
7468 if( p != NULL && canAdjudicate) {
7469 if(engineOpponent) {
7470 SendToProgram("force\n", engineOpponent); // suppress reply
7471 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7473 GameEnds( GameIsDrawn, p, GE_XBOARD );
7478 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7479 if(engineOpponent) {
7480 SendToProgram("force\n", engineOpponent); // suppress reply
7481 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7483 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7489 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7490 { // [HGM] book: this routine intercepts moves to simulate book replies
7491 char *bookHit = NULL;
7493 //first determine if the incoming move brings opponent into his book
7494 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7495 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7496 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7497 if(bookHit != NULL && !cps->bookSuspend) {
7498 // make sure opponent is not going to reply after receiving move to book position
7499 SendToProgram("force\n", cps);
7500 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7502 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7503 // now arrange restart after book miss
7505 // after a book hit we never send 'go', and the code after the call to this routine
7506 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7508 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7509 SendToProgram(buf, cps);
7510 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7511 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7512 SendToProgram("go\n", cps);
7513 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7514 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7515 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7516 SendToProgram("go\n", cps);
7517 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7519 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7523 ChessProgramState *savedState;
7524 void DeferredBookMove(void)
7526 if(savedState->lastPing != savedState->lastPong)
7527 ScheduleDelayedEvent(DeferredBookMove, 10);
7529 HandleMachineMove(savedMessage, savedState);
7533 HandleMachineMove(message, cps)
7535 ChessProgramState *cps;
7537 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7538 char realname[MSG_SIZ];
7539 int fromX, fromY, toX, toY;
7548 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7550 * Kludge to ignore BEL characters
7552 while (*message == '\007') message++;
7555 * [HGM] engine debug message: ignore lines starting with '#' character
7557 if(cps->debug && *message == '#') return;
7560 * Look for book output
7562 if (cps == &first && bookRequested) {
7563 if (message[0] == '\t' || message[0] == ' ') {
7564 /* Part of the book output is here; append it */
7565 strcat(bookOutput, message);
7566 strcat(bookOutput, " \n");
7568 } else if (bookOutput[0] != NULLCHAR) {
7569 /* All of book output has arrived; display it */
7570 char *p = bookOutput;
7571 while (*p != NULLCHAR) {
7572 if (*p == '\t') *p = ' ';
7575 DisplayInformation(bookOutput);
7576 bookRequested = FALSE;
7577 /* Fall through to parse the current output */
7582 * Look for machine move.
7584 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7585 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7587 /* This method is only useful on engines that support ping */
7588 if (cps->lastPing != cps->lastPong) {
7589 if (gameMode == BeginningOfGame) {
7590 /* Extra move from before last new; ignore */
7591 if (appData.debugMode) {
7592 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7595 if (appData.debugMode) {
7596 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7597 cps->which, gameMode);
7600 SendToProgram("undo\n", cps);
7606 case BeginningOfGame:
7607 /* Extra move from before last reset; ignore */
7608 if (appData.debugMode) {
7609 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7616 /* Extra move after we tried to stop. The mode test is
7617 not a reliable way of detecting this problem, but it's
7618 the best we can do on engines that don't support ping.
7620 if (appData.debugMode) {
7621 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7622 cps->which, gameMode);
7624 SendToProgram("undo\n", cps);
7627 case MachinePlaysWhite:
7628 case IcsPlayingWhite:
7629 machineWhite = TRUE;
7632 case MachinePlaysBlack:
7633 case IcsPlayingBlack:
7634 machineWhite = FALSE;
7637 case TwoMachinesPlay:
7638 machineWhite = (cps->twoMachinesColor[0] == 'w');
7641 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7642 if (appData.debugMode) {
7644 "Ignoring move out of turn by %s, gameMode %d"
7645 ", forwardMost %d\n",
7646 cps->which, gameMode, forwardMostMove);
7651 if (appData.debugMode) { int f = forwardMostMove;
7652 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7653 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7654 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7656 if(cps->alphaRank) AlphaRank(machineMove, 4);
7657 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7658 &fromX, &fromY, &toX, &toY, &promoChar)) {
7659 /* Machine move could not be parsed; ignore it. */
7660 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7661 machineMove, _(cps->which));
7662 DisplayError(buf1, 0);
7663 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7664 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7665 if (gameMode == TwoMachinesPlay) {
7666 GameEnds(machineWhite ? BlackWins : WhiteWins,
7672 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7673 /* So we have to redo legality test with true e.p. status here, */
7674 /* to make sure an illegal e.p. capture does not slip through, */
7675 /* to cause a forfeit on a justified illegal-move complaint */
7676 /* of the opponent. */
7677 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7679 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7680 fromY, fromX, toY, toX, promoChar);
7681 if (appData.debugMode) {
7683 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7684 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7685 fprintf(debugFP, "castling rights\n");
7687 if(moveType == IllegalMove) {
7688 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7689 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7690 GameEnds(machineWhite ? BlackWins : WhiteWins,
7693 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7694 /* [HGM] Kludge to handle engines that send FRC-style castling
7695 when they shouldn't (like TSCP-Gothic) */
7697 case WhiteASideCastleFR:
7698 case BlackASideCastleFR:
7700 currentMoveString[2]++;
7702 case WhiteHSideCastleFR:
7703 case BlackHSideCastleFR:
7705 currentMoveString[2]--;
7707 default: ; // nothing to do, but suppresses warning of pedantic compilers
7710 hintRequested = FALSE;
7711 lastHint[0] = NULLCHAR;
7712 bookRequested = FALSE;
7713 /* Program may be pondering now */
7714 cps->maybeThinking = TRUE;
7715 if (cps->sendTime == 2) cps->sendTime = 1;
7716 if (cps->offeredDraw) cps->offeredDraw--;
7718 /* [AS] Save move info*/
7719 pvInfoList[ forwardMostMove ].score = programStats.score;
7720 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7721 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7723 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7725 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7726 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7729 while( count < adjudicateLossPlies ) {
7730 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7733 score = -score; /* Flip score for winning side */
7736 if( score > adjudicateLossThreshold ) {
7743 if( count >= adjudicateLossPlies ) {
7744 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7746 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7747 "Xboard adjudication",
7754 if(Adjudicate(cps)) {
7755 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7756 return; // [HGM] adjudicate: for all automatic game ends
7760 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7762 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7763 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7765 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7767 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7769 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7770 char buf[3*MSG_SIZ];
7772 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7773 programStats.score / 100.,
7775 programStats.time / 100.,
7776 (unsigned int)programStats.nodes,
7777 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7778 programStats.movelist);
7780 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7785 /* [AS] Clear stats for next move */
7786 ClearProgramStats();
7787 thinkOutput[0] = NULLCHAR;
7788 hiddenThinkOutputState = 0;
7791 if (gameMode == TwoMachinesPlay) {
7792 /* [HGM] relaying draw offers moved to after reception of move */
7793 /* and interpreting offer as claim if it brings draw condition */
7794 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7795 SendToProgram("draw\n", cps->other);
7797 if (cps->other->sendTime) {
7798 SendTimeRemaining(cps->other,
7799 cps->other->twoMachinesColor[0] == 'w');
7801 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7802 if (firstMove && !bookHit) {
7804 if (cps->other->useColors) {
7805 SendToProgram(cps->other->twoMachinesColor, cps->other);
7807 SendToProgram("go\n", cps->other);
7809 cps->other->maybeThinking = TRUE;
7812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7814 if (!pausing && appData.ringBellAfterMoves) {
7819 * Reenable menu items that were disabled while
7820 * machine was thinking
7822 if (gameMode != TwoMachinesPlay)
7823 SetUserThinkingEnables();
7825 // [HGM] book: after book hit opponent has received move and is now in force mode
7826 // force the book reply into it, and then fake that it outputted this move by jumping
7827 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7829 static char bookMove[MSG_SIZ]; // a bit generous?
7831 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7832 strcat(bookMove, bookHit);
7835 programStats.nodes = programStats.depth = programStats.time =
7836 programStats.score = programStats.got_only_move = 0;
7837 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7839 if(cps->lastPing != cps->lastPong) {
7840 savedMessage = message; // args for deferred call
7842 ScheduleDelayedEvent(DeferredBookMove, 10);
7851 /* Set special modes for chess engines. Later something general
7852 * could be added here; for now there is just one kludge feature,
7853 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7854 * when "xboard" is given as an interactive command.
7856 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7857 cps->useSigint = FALSE;
7858 cps->useSigterm = FALSE;
7860 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7861 ParseFeatures(message+8, cps);
7862 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7865 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7866 int dummy, s=6; char buf[MSG_SIZ];
7867 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7868 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7869 ParseFEN(boards[0], &dummy, message+s);
7870 DrawPosition(TRUE, boards[0]);
7871 startedFromSetupPosition = TRUE;
7874 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7875 * want this, I was asked to put it in, and obliged.
7877 if (!strncmp(message, "setboard ", 9)) {
7878 Board initial_position;
7880 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7882 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7883 DisplayError(_("Bad FEN received from engine"), 0);
7887 CopyBoard(boards[0], initial_position);
7888 initialRulePlies = FENrulePlies;
7889 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7890 else gameMode = MachinePlaysBlack;
7891 DrawPosition(FALSE, boards[currentMove]);
7897 * Look for communication commands
7899 if (!strncmp(message, "telluser ", 9)) {
7900 if(message[9] == '\\' && message[10] == '\\')
7901 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7902 DisplayNote(message + 9);
7905 if (!strncmp(message, "tellusererror ", 14)) {
7907 if(message[14] == '\\' && message[15] == '\\')
7908 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7909 DisplayError(message + 14, 0);
7912 if (!strncmp(message, "tellopponent ", 13)) {
7913 if (appData.icsActive) {
7915 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7919 DisplayNote(message + 13);
7923 if (!strncmp(message, "tellothers ", 11)) {
7924 if (appData.icsActive) {
7926 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7932 if (!strncmp(message, "tellall ", 8)) {
7933 if (appData.icsActive) {
7935 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7939 DisplayNote(message + 8);
7943 if (strncmp(message, "warning", 7) == 0) {
7944 /* Undocumented feature, use tellusererror in new code */
7945 DisplayError(message, 0);
7948 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7949 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7950 strcat(realname, " query");
7951 AskQuestion(realname, buf2, buf1, cps->pr);
7954 /* Commands from the engine directly to ICS. We don't allow these to be
7955 * sent until we are logged on. Crafty kibitzes have been known to
7956 * interfere with the login process.
7959 if (!strncmp(message, "tellics ", 8)) {
7960 SendToICS(message + 8);
7964 if (!strncmp(message, "tellicsnoalias ", 15)) {
7965 SendToICS(ics_prefix);
7966 SendToICS(message + 15);
7970 /* The following are for backward compatibility only */
7971 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7972 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7973 SendToICS(ics_prefix);
7979 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7983 * If the move is illegal, cancel it and redraw the board.
7984 * Also deal with other error cases. Matching is rather loose
7985 * here to accommodate engines written before the spec.
7987 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7988 strncmp(message, "Error", 5) == 0) {
7989 if (StrStr(message, "name") ||
7990 StrStr(message, "rating") || StrStr(message, "?") ||
7991 StrStr(message, "result") || StrStr(message, "board") ||
7992 StrStr(message, "bk") || StrStr(message, "computer") ||
7993 StrStr(message, "variant") || StrStr(message, "hint") ||
7994 StrStr(message, "random") || StrStr(message, "depth") ||
7995 StrStr(message, "accepted")) {
7998 if (StrStr(message, "protover")) {
7999 /* Program is responding to input, so it's apparently done
8000 initializing, and this error message indicates it is
8001 protocol version 1. So we don't need to wait any longer
8002 for it to initialize and send feature commands. */
8003 FeatureDone(cps, 1);
8004 cps->protocolVersion = 1;
8007 cps->maybeThinking = FALSE;
8009 if (StrStr(message, "draw")) {
8010 /* Program doesn't have "draw" command */
8011 cps->sendDrawOffers = 0;
8014 if (cps->sendTime != 1 &&
8015 (StrStr(message, "time") || StrStr(message, "otim"))) {
8016 /* Program apparently doesn't have "time" or "otim" command */
8020 if (StrStr(message, "analyze")) {
8021 cps->analysisSupport = FALSE;
8022 cps->analyzing = FALSE;
8024 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8025 DisplayError(buf2, 0);
8028 if (StrStr(message, "(no matching move)st")) {
8029 /* Special kludge for GNU Chess 4 only */
8030 cps->stKludge = TRUE;
8031 SendTimeControl(cps, movesPerSession, timeControl,
8032 timeIncrement, appData.searchDepth,
8036 if (StrStr(message, "(no matching move)sd")) {
8037 /* Special kludge for GNU Chess 4 only */
8038 cps->sdKludge = TRUE;
8039 SendTimeControl(cps, movesPerSession, timeControl,
8040 timeIncrement, appData.searchDepth,
8044 if (!StrStr(message, "llegal")) {
8047 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8048 gameMode == IcsIdle) return;
8049 if (forwardMostMove <= backwardMostMove) return;
8050 if (pausing) PauseEvent();
8051 if(appData.forceIllegal) {
8052 // [HGM] illegal: machine refused move; force position after move into it
8053 SendToProgram("force\n", cps);
8054 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8055 // we have a real problem now, as SendBoard will use the a2a3 kludge
8056 // when black is to move, while there might be nothing on a2 or black
8057 // might already have the move. So send the board as if white has the move.
8058 // But first we must change the stm of the engine, as it refused the last move
8059 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8060 if(WhiteOnMove(forwardMostMove)) {
8061 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8062 SendBoard(cps, forwardMostMove); // kludgeless board
8064 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8065 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8066 SendBoard(cps, forwardMostMove+1); // kludgeless board
8068 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8069 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8070 gameMode == TwoMachinesPlay)
8071 SendToProgram("go\n", cps);
8074 if (gameMode == PlayFromGameFile) {
8075 /* Stop reading this game file */
8076 gameMode = EditGame;
8079 /* [HGM] illegal-move claim should forfeit game when Xboard */
8080 /* only passes fully legal moves */
8081 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8082 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8083 "False illegal-move claim", GE_XBOARD );
8084 return; // do not take back move we tested as valid
8086 currentMove = forwardMostMove-1;
8087 DisplayMove(currentMove-1); /* before DisplayMoveError */
8088 SwitchClocks(forwardMostMove-1); // [HGM] race
8089 DisplayBothClocks();
8090 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8091 parseList[currentMove], _(cps->which));
8092 DisplayMoveError(buf1);
8093 DrawPosition(FALSE, boards[currentMove]);
8096 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8097 /* Program has a broken "time" command that
8098 outputs a string not ending in newline.
8104 * If chess program startup fails, exit with an error message.
8105 * Attempts to recover here are futile.
8107 if ((StrStr(message, "unknown host") != NULL)
8108 || (StrStr(message, "No remote directory") != NULL)
8109 || (StrStr(message, "not found") != NULL)
8110 || (StrStr(message, "No such file") != NULL)
8111 || (StrStr(message, "can't alloc") != NULL)
8112 || (StrStr(message, "Permission denied") != NULL)) {
8114 cps->maybeThinking = FALSE;
8115 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8116 _(cps->which), cps->program, cps->host, message);
8117 RemoveInputSource(cps->isr);
8118 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8119 if(cps == &first) appData.noChessProgram = TRUE;
8120 DisplayError(buf1, 0);
8126 * Look for hint output
8128 if (sscanf(message, "Hint: %s", buf1) == 1) {
8129 if (cps == &first && hintRequested) {
8130 hintRequested = FALSE;
8131 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8132 &fromX, &fromY, &toX, &toY, &promoChar)) {
8133 (void) CoordsToAlgebraic(boards[forwardMostMove],
8134 PosFlags(forwardMostMove),
8135 fromY, fromX, toY, toX, promoChar, buf1);
8136 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8137 DisplayInformation(buf2);
8139 /* Hint move could not be parsed!? */
8140 snprintf(buf2, sizeof(buf2),
8141 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8142 buf1, _(cps->which));
8143 DisplayError(buf2, 0);
8146 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8152 * Ignore other messages if game is not in progress
8154 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8155 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8158 * look for win, lose, draw, or draw offer
8160 if (strncmp(message, "1-0", 3) == 0) {
8161 char *p, *q, *r = "";
8162 p = strchr(message, '{');
8170 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8172 } else if (strncmp(message, "0-1", 3) == 0) {
8173 char *p, *q, *r = "";
8174 p = strchr(message, '{');
8182 /* Kludge for Arasan 4.1 bug */
8183 if (strcmp(r, "Black resigns") == 0) {
8184 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8187 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8189 } else if (strncmp(message, "1/2", 3) == 0) {
8190 char *p, *q, *r = "";
8191 p = strchr(message, '{');
8200 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8203 } else if (strncmp(message, "White resign", 12) == 0) {
8204 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8206 } else if (strncmp(message, "Black resign", 12) == 0) {
8207 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8209 } else if (strncmp(message, "White matches", 13) == 0 ||
8210 strncmp(message, "Black matches", 13) == 0 ) {
8211 /* [HGM] ignore GNUShogi noises */
8213 } else if (strncmp(message, "White", 5) == 0 &&
8214 message[5] != '(' &&
8215 StrStr(message, "Black") == NULL) {
8216 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8218 } else if (strncmp(message, "Black", 5) == 0 &&
8219 message[5] != '(') {
8220 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8222 } else if (strcmp(message, "resign") == 0 ||
8223 strcmp(message, "computer resigns") == 0) {
8225 case MachinePlaysBlack:
8226 case IcsPlayingBlack:
8227 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8229 case MachinePlaysWhite:
8230 case IcsPlayingWhite:
8231 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8233 case TwoMachinesPlay:
8234 if (cps->twoMachinesColor[0] == 'w')
8235 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8237 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8244 } else if (strncmp(message, "opponent mates", 14) == 0) {
8246 case MachinePlaysBlack:
8247 case IcsPlayingBlack:
8248 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8250 case MachinePlaysWhite:
8251 case IcsPlayingWhite:
8252 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8254 case TwoMachinesPlay:
8255 if (cps->twoMachinesColor[0] == 'w')
8256 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8258 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8265 } else if (strncmp(message, "computer mates", 14) == 0) {
8267 case MachinePlaysBlack:
8268 case IcsPlayingBlack:
8269 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8271 case MachinePlaysWhite:
8272 case IcsPlayingWhite:
8273 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8275 case TwoMachinesPlay:
8276 if (cps->twoMachinesColor[0] == 'w')
8277 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8279 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8286 } else if (strncmp(message, "checkmate", 9) == 0) {
8287 if (WhiteOnMove(forwardMostMove)) {
8288 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8290 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8293 } else if (strstr(message, "Draw") != NULL ||
8294 strstr(message, "game is a draw") != NULL) {
8295 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8297 } else if (strstr(message, "offer") != NULL &&
8298 strstr(message, "draw") != NULL) {
8300 if (appData.zippyPlay && first.initDone) {
8301 /* Relay offer to ICS */
8302 SendToICS(ics_prefix);
8303 SendToICS("draw\n");
8306 cps->offeredDraw = 2; /* valid until this engine moves twice */
8307 if (gameMode == TwoMachinesPlay) {
8308 if (cps->other->offeredDraw) {
8309 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8310 /* [HGM] in two-machine mode we delay relaying draw offer */
8311 /* until after we also have move, to see if it is really claim */
8313 } else if (gameMode == MachinePlaysWhite ||
8314 gameMode == MachinePlaysBlack) {
8315 if (userOfferedDraw) {
8316 DisplayInformation(_("Machine accepts your draw offer"));
8317 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8319 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8326 * Look for thinking output
8328 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8329 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8331 int plylev, mvleft, mvtot, curscore, time;
8332 char mvname[MOVE_LEN];
8336 int prefixHint = FALSE;
8337 mvname[0] = NULLCHAR;
8340 case MachinePlaysBlack:
8341 case IcsPlayingBlack:
8342 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8344 case MachinePlaysWhite:
8345 case IcsPlayingWhite:
8346 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8351 case IcsObserving: /* [DM] icsEngineAnalyze */
8352 if (!appData.icsEngineAnalyze) ignore = TRUE;
8354 case TwoMachinesPlay:
8355 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8365 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8367 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8368 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8370 if (plyext != ' ' && plyext != '\t') {
8374 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8375 if( cps->scoreIsAbsolute &&
8376 ( gameMode == MachinePlaysBlack ||
8377 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8378 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8379 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8380 !WhiteOnMove(currentMove)
8383 curscore = -curscore;
8387 tempStats.depth = plylev;
8388 tempStats.nodes = nodes;
8389 tempStats.time = time;
8390 tempStats.score = curscore;
8391 tempStats.got_only_move = 0;
8393 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8396 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8397 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8398 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8399 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8400 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8401 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8402 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8403 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8406 /* Buffer overflow protection */
8407 if (buf1[0] != NULLCHAR) {
8408 if (strlen(buf1) >= sizeof(tempStats.movelist)
8409 && appData.debugMode) {
8411 "PV is too long; using the first %u bytes.\n",
8412 (unsigned) sizeof(tempStats.movelist) - 1);
8415 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8417 sprintf(tempStats.movelist, " no PV\n");
8420 if (tempStats.seen_stat) {
8421 tempStats.ok_to_send = 1;
8424 if (strchr(tempStats.movelist, '(') != NULL) {
8425 tempStats.line_is_book = 1;
8426 tempStats.nr_moves = 0;
8427 tempStats.moves_left = 0;
8429 tempStats.line_is_book = 0;
8432 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8433 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8435 SendProgramStatsToFrontend( cps, &tempStats );
8438 [AS] Protect the thinkOutput buffer from overflow... this
8439 is only useful if buf1 hasn't overflowed first!
8441 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8443 (gameMode == TwoMachinesPlay ?
8444 ToUpper(cps->twoMachinesColor[0]) : ' '),
8445 ((double) curscore) / 100.0,
8446 prefixHint ? lastHint : "",
8447 prefixHint ? " " : "" );
8449 if( buf1[0] != NULLCHAR ) {
8450 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8452 if( strlen(buf1) > max_len ) {
8453 if( appData.debugMode) {
8454 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8456 buf1[max_len+1] = '\0';
8459 strcat( thinkOutput, buf1 );
8462 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8463 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8464 DisplayMove(currentMove - 1);
8468 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8469 /* crafty (9.25+) says "(only move) <move>"
8470 * if there is only 1 legal move
8472 sscanf(p, "(only move) %s", buf1);
8473 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8474 sprintf(programStats.movelist, "%s (only move)", buf1);
8475 programStats.depth = 1;
8476 programStats.nr_moves = 1;
8477 programStats.moves_left = 1;
8478 programStats.nodes = 1;
8479 programStats.time = 1;
8480 programStats.got_only_move = 1;
8482 /* Not really, but we also use this member to
8483 mean "line isn't going to change" (Crafty
8484 isn't searching, so stats won't change) */
8485 programStats.line_is_book = 1;
8487 SendProgramStatsToFrontend( cps, &programStats );
8489 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8490 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8491 DisplayMove(currentMove - 1);
8494 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8495 &time, &nodes, &plylev, &mvleft,
8496 &mvtot, mvname) >= 5) {
8497 /* The stat01: line is from Crafty (9.29+) in response
8498 to the "." command */
8499 programStats.seen_stat = 1;
8500 cps->maybeThinking = TRUE;
8502 if (programStats.got_only_move || !appData.periodicUpdates)
8505 programStats.depth = plylev;
8506 programStats.time = time;
8507 programStats.nodes = nodes;
8508 programStats.moves_left = mvleft;
8509 programStats.nr_moves = mvtot;
8510 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8511 programStats.ok_to_send = 1;
8512 programStats.movelist[0] = '\0';
8514 SendProgramStatsToFrontend( cps, &programStats );
8518 } else if (strncmp(message,"++",2) == 0) {
8519 /* Crafty 9.29+ outputs this */
8520 programStats.got_fail = 2;
8523 } else if (strncmp(message,"--",2) == 0) {
8524 /* Crafty 9.29+ outputs this */
8525 programStats.got_fail = 1;
8528 } else if (thinkOutput[0] != NULLCHAR &&
8529 strncmp(message, " ", 4) == 0) {
8530 unsigned message_len;
8533 while (*p && *p == ' ') p++;
8535 message_len = strlen( p );
8537 /* [AS] Avoid buffer overflow */
8538 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8539 strcat(thinkOutput, " ");
8540 strcat(thinkOutput, p);
8543 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8544 strcat(programStats.movelist, " ");
8545 strcat(programStats.movelist, p);
8548 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8549 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8550 DisplayMove(currentMove - 1);
8558 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8559 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8561 ChessProgramStats cpstats;
8563 if (plyext != ' ' && plyext != '\t') {
8567 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8568 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8569 curscore = -curscore;
8572 cpstats.depth = plylev;
8573 cpstats.nodes = nodes;
8574 cpstats.time = time;
8575 cpstats.score = curscore;
8576 cpstats.got_only_move = 0;
8577 cpstats.movelist[0] = '\0';
8579 if (buf1[0] != NULLCHAR) {
8580 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8583 cpstats.ok_to_send = 0;
8584 cpstats.line_is_book = 0;
8585 cpstats.nr_moves = 0;
8586 cpstats.moves_left = 0;
8588 SendProgramStatsToFrontend( cps, &cpstats );
8595 /* Parse a game score from the character string "game", and
8596 record it as the history of the current game. The game
8597 score is NOT assumed to start from the standard position.
8598 The display is not updated in any way.
8601 ParseGameHistory(game)
8605 int fromX, fromY, toX, toY, boardIndex;
8610 if (appData.debugMode)
8611 fprintf(debugFP, "Parsing game history: %s\n", game);
8613 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8614 gameInfo.site = StrSave(appData.icsHost);
8615 gameInfo.date = PGNDate();
8616 gameInfo.round = StrSave("-");
8618 /* Parse out names of players */
8619 while (*game == ' ') game++;
8621 while (*game != ' ') *p++ = *game++;
8623 gameInfo.white = StrSave(buf);
8624 while (*game == ' ') game++;
8626 while (*game != ' ' && *game != '\n') *p++ = *game++;
8628 gameInfo.black = StrSave(buf);
8631 boardIndex = blackPlaysFirst ? 1 : 0;
8634 yyboardindex = boardIndex;
8635 moveType = (ChessMove) Myylex();
8637 case IllegalMove: /* maybe suicide chess, etc. */
8638 if (appData.debugMode) {
8639 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8640 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8641 setbuf(debugFP, NULL);
8643 case WhitePromotion:
8644 case BlackPromotion:
8645 case WhiteNonPromotion:
8646 case BlackNonPromotion:
8648 case WhiteCapturesEnPassant:
8649 case BlackCapturesEnPassant:
8650 case WhiteKingSideCastle:
8651 case WhiteQueenSideCastle:
8652 case BlackKingSideCastle:
8653 case BlackQueenSideCastle:
8654 case WhiteKingSideCastleWild:
8655 case WhiteQueenSideCastleWild:
8656 case BlackKingSideCastleWild:
8657 case BlackQueenSideCastleWild:
8659 case WhiteHSideCastleFR:
8660 case WhiteASideCastleFR:
8661 case BlackHSideCastleFR:
8662 case BlackASideCastleFR:
8664 fromX = currentMoveString[0] - AAA;
8665 fromY = currentMoveString[1] - ONE;
8666 toX = currentMoveString[2] - AAA;
8667 toY = currentMoveString[3] - ONE;
8668 promoChar = currentMoveString[4];
8672 fromX = moveType == WhiteDrop ?
8673 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8674 (int) CharToPiece(ToLower(currentMoveString[0]));
8676 toX = currentMoveString[2] - AAA;
8677 toY = currentMoveString[3] - ONE;
8678 promoChar = NULLCHAR;
8682 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8683 if (appData.debugMode) {
8684 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8685 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8686 setbuf(debugFP, NULL);
8688 DisplayError(buf, 0);
8690 case ImpossibleMove:
8692 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8693 if (appData.debugMode) {
8694 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8695 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8696 setbuf(debugFP, NULL);
8698 DisplayError(buf, 0);
8701 if (boardIndex < backwardMostMove) {
8702 /* Oops, gap. How did that happen? */
8703 DisplayError(_("Gap in move list"), 0);
8706 backwardMostMove = blackPlaysFirst ? 1 : 0;
8707 if (boardIndex > forwardMostMove) {
8708 forwardMostMove = boardIndex;
8712 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8713 strcat(parseList[boardIndex-1], " ");
8714 strcat(parseList[boardIndex-1], yy_text);
8726 case GameUnfinished:
8727 if (gameMode == IcsExamining) {
8728 if (boardIndex < backwardMostMove) {
8729 /* Oops, gap. How did that happen? */
8732 backwardMostMove = blackPlaysFirst ? 1 : 0;
8735 gameInfo.result = moveType;
8736 p = strchr(yy_text, '{');
8737 if (p == NULL) p = strchr(yy_text, '(');
8740 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8742 q = strchr(p, *p == '{' ? '}' : ')');
8743 if (q != NULL) *q = NULLCHAR;
8746 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8747 gameInfo.resultDetails = StrSave(p);
8750 if (boardIndex >= forwardMostMove &&
8751 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8752 backwardMostMove = blackPlaysFirst ? 1 : 0;
8755 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8756 fromY, fromX, toY, toX, promoChar,
8757 parseList[boardIndex]);
8758 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8759 /* currentMoveString is set as a side-effect of yylex */
8760 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8761 strcat(moveList[boardIndex], "\n");
8763 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8764 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8770 if(gameInfo.variant != VariantShogi)
8771 strcat(parseList[boardIndex - 1], "+");
8775 strcat(parseList[boardIndex - 1], "#");
8782 /* Apply a move to the given board */
8784 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8785 int fromX, fromY, toX, toY;
8789 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8790 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8792 /* [HGM] compute & store e.p. status and castling rights for new position */
8793 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8795 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8796 oldEP = (signed char)board[EP_STATUS];
8797 board[EP_STATUS] = EP_NONE;
8799 if( board[toY][toX] != EmptySquare )
8800 board[EP_STATUS] = EP_CAPTURE;
8802 if (fromY == DROP_RANK) {
8804 piece = board[toY][toX] = (ChessSquare) fromX;
8808 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8809 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8810 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8812 if( board[fromY][fromX] == WhitePawn ) {
8813 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8814 board[EP_STATUS] = EP_PAWN_MOVE;
8816 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8817 gameInfo.variant != VariantBerolina || toX < fromX)
8818 board[EP_STATUS] = toX | berolina;
8819 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8820 gameInfo.variant != VariantBerolina || toX > fromX)
8821 board[EP_STATUS] = toX;
8824 if( board[fromY][fromX] == BlackPawn ) {
8825 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8826 board[EP_STATUS] = EP_PAWN_MOVE;
8827 if( toY-fromY== -2) {
8828 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8829 gameInfo.variant != VariantBerolina || toX < fromX)
8830 board[EP_STATUS] = toX | berolina;
8831 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8832 gameInfo.variant != VariantBerolina || toX > fromX)
8833 board[EP_STATUS] = toX;
8837 for(i=0; i<nrCastlingRights; i++) {
8838 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8839 board[CASTLING][i] == toX && castlingRank[i] == toY
8840 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8843 if (fromX == toX && fromY == toY) return;
8845 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8846 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8847 if(gameInfo.variant == VariantKnightmate)
8848 king += (int) WhiteUnicorn - (int) WhiteKing;
8850 /* Code added by Tord: */
8851 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8852 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8853 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8854 board[fromY][fromX] = EmptySquare;
8855 board[toY][toX] = EmptySquare;
8856 if((toX > fromX) != (piece == WhiteRook)) {
8857 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8859 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8861 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8862 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8863 board[fromY][fromX] = EmptySquare;
8864 board[toY][toX] = EmptySquare;
8865 if((toX > fromX) != (piece == BlackRook)) {
8866 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8868 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8870 /* End of code added by Tord */
8872 } else if (board[fromY][fromX] == king
8873 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8874 && toY == fromY && toX > fromX+1) {
8875 board[fromY][fromX] = EmptySquare;
8876 board[toY][toX] = king;
8877 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8878 board[fromY][BOARD_RGHT-1] = EmptySquare;
8879 } else if (board[fromY][fromX] == king
8880 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8881 && toY == fromY && toX < fromX-1) {
8882 board[fromY][fromX] = EmptySquare;
8883 board[toY][toX] = king;
8884 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8885 board[fromY][BOARD_LEFT] = EmptySquare;
8886 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8887 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8888 && toY >= BOARD_HEIGHT-promoRank
8890 /* white pawn promotion */
8891 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8892 if (board[toY][toX] == EmptySquare) {
8893 board[toY][toX] = WhiteQueen;
8895 if(gameInfo.variant==VariantBughouse ||
8896 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8897 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8898 board[fromY][fromX] = EmptySquare;
8899 } else if ((fromY == BOARD_HEIGHT-4)
8901 && gameInfo.variant != VariantXiangqi
8902 && gameInfo.variant != VariantBerolina
8903 && (board[fromY][fromX] == WhitePawn)
8904 && (board[toY][toX] == EmptySquare)) {
8905 board[fromY][fromX] = EmptySquare;
8906 board[toY][toX] = WhitePawn;
8907 captured = board[toY - 1][toX];
8908 board[toY - 1][toX] = EmptySquare;
8909 } else if ((fromY == BOARD_HEIGHT-4)
8911 && gameInfo.variant == VariantBerolina
8912 && (board[fromY][fromX] == WhitePawn)
8913 && (board[toY][toX] == EmptySquare)) {
8914 board[fromY][fromX] = EmptySquare;
8915 board[toY][toX] = WhitePawn;
8916 if(oldEP & EP_BEROLIN_A) {
8917 captured = board[fromY][fromX-1];
8918 board[fromY][fromX-1] = EmptySquare;
8919 }else{ captured = board[fromY][fromX+1];
8920 board[fromY][fromX+1] = EmptySquare;
8922 } else if (board[fromY][fromX] == king
8923 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8924 && toY == fromY && toX > fromX+1) {
8925 board[fromY][fromX] = EmptySquare;
8926 board[toY][toX] = king;
8927 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8928 board[fromY][BOARD_RGHT-1] = EmptySquare;
8929 } else if (board[fromY][fromX] == king
8930 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8931 && toY == fromY && toX < fromX-1) {
8932 board[fromY][fromX] = EmptySquare;
8933 board[toY][toX] = king;
8934 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8935 board[fromY][BOARD_LEFT] = EmptySquare;
8936 } else if (fromY == 7 && fromX == 3
8937 && board[fromY][fromX] == BlackKing
8938 && toY == 7 && toX == 5) {
8939 board[fromY][fromX] = EmptySquare;
8940 board[toY][toX] = BlackKing;
8941 board[fromY][7] = EmptySquare;
8942 board[toY][4] = BlackRook;
8943 } else if (fromY == 7 && fromX == 3
8944 && board[fromY][fromX] == BlackKing
8945 && toY == 7 && toX == 1) {
8946 board[fromY][fromX] = EmptySquare;
8947 board[toY][toX] = BlackKing;
8948 board[fromY][0] = EmptySquare;
8949 board[toY][2] = BlackRook;
8950 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8951 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8954 /* black pawn promotion */
8955 board[toY][toX] = CharToPiece(ToLower(promoChar));
8956 if (board[toY][toX] == EmptySquare) {
8957 board[toY][toX] = BlackQueen;
8959 if(gameInfo.variant==VariantBughouse ||
8960 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8961 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8962 board[fromY][fromX] = EmptySquare;
8963 } else if ((fromY == 3)
8965 && gameInfo.variant != VariantXiangqi
8966 && gameInfo.variant != VariantBerolina
8967 && (board[fromY][fromX] == BlackPawn)
8968 && (board[toY][toX] == EmptySquare)) {
8969 board[fromY][fromX] = EmptySquare;
8970 board[toY][toX] = BlackPawn;
8971 captured = board[toY + 1][toX];
8972 board[toY + 1][toX] = EmptySquare;
8973 } else if ((fromY == 3)
8975 && gameInfo.variant == VariantBerolina
8976 && (board[fromY][fromX] == BlackPawn)
8977 && (board[toY][toX] == EmptySquare)) {
8978 board[fromY][fromX] = EmptySquare;
8979 board[toY][toX] = BlackPawn;
8980 if(oldEP & EP_BEROLIN_A) {
8981 captured = board[fromY][fromX-1];
8982 board[fromY][fromX-1] = EmptySquare;
8983 }else{ captured = board[fromY][fromX+1];
8984 board[fromY][fromX+1] = EmptySquare;
8987 board[toY][toX] = board[fromY][fromX];
8988 board[fromY][fromX] = EmptySquare;
8992 if (gameInfo.holdingsWidth != 0) {
8994 /* !!A lot more code needs to be written to support holdings */
8995 /* [HGM] OK, so I have written it. Holdings are stored in the */
8996 /* penultimate board files, so they are automaticlly stored */
8997 /* in the game history. */
8998 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8999 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9000 /* Delete from holdings, by decreasing count */
9001 /* and erasing image if necessary */
9002 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9003 if(p < (int) BlackPawn) { /* white drop */
9004 p -= (int)WhitePawn;
9005 p = PieceToNumber((ChessSquare)p);
9006 if(p >= gameInfo.holdingsSize) p = 0;
9007 if(--board[p][BOARD_WIDTH-2] <= 0)
9008 board[p][BOARD_WIDTH-1] = EmptySquare;
9009 if((int)board[p][BOARD_WIDTH-2] < 0)
9010 board[p][BOARD_WIDTH-2] = 0;
9011 } else { /* black drop */
9012 p -= (int)BlackPawn;
9013 p = PieceToNumber((ChessSquare)p);
9014 if(p >= gameInfo.holdingsSize) p = 0;
9015 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9016 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9017 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9018 board[BOARD_HEIGHT-1-p][1] = 0;
9021 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9022 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9023 /* [HGM] holdings: Add to holdings, if holdings exist */
9024 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9025 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9026 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9029 if (p >= (int) BlackPawn) {
9030 p -= (int)BlackPawn;
9031 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9032 /* in Shogi restore piece to its original first */
9033 captured = (ChessSquare) (DEMOTED captured);
9036 p = PieceToNumber((ChessSquare)p);
9037 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9038 board[p][BOARD_WIDTH-2]++;
9039 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9041 p -= (int)WhitePawn;
9042 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9043 captured = (ChessSquare) (DEMOTED captured);
9046 p = PieceToNumber((ChessSquare)p);
9047 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9048 board[BOARD_HEIGHT-1-p][1]++;
9049 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9052 } else if (gameInfo.variant == VariantAtomic) {
9053 if (captured != EmptySquare) {
9055 for (y = toY-1; y <= toY+1; y++) {
9056 for (x = toX-1; x <= toX+1; x++) {
9057 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9058 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9059 board[y][x] = EmptySquare;
9063 board[toY][toX] = EmptySquare;
9066 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9067 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9069 if(promoChar == '+') {
9070 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9071 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9072 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9073 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9075 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9076 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9077 // [HGM] superchess: take promotion piece out of holdings
9078 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9079 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9080 if(!--board[k][BOARD_WIDTH-2])
9081 board[k][BOARD_WIDTH-1] = EmptySquare;
9083 if(!--board[BOARD_HEIGHT-1-k][1])
9084 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9090 /* Updates forwardMostMove */
9092 MakeMove(fromX, fromY, toX, toY, promoChar)
9093 int fromX, fromY, toX, toY;
9096 // forwardMostMove++; // [HGM] bare: moved downstream
9098 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9099 int timeLeft; static int lastLoadFlag=0; int king, piece;
9100 piece = boards[forwardMostMove][fromY][fromX];
9101 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9102 if(gameInfo.variant == VariantKnightmate)
9103 king += (int) WhiteUnicorn - (int) WhiteKing;
9104 if(forwardMostMove == 0) {
9106 fprintf(serverMoves, "%s;", second.tidy);
9107 fprintf(serverMoves, "%s;", first.tidy);
9108 if(!blackPlaysFirst)
9109 fprintf(serverMoves, "%s;", second.tidy);
9110 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9111 lastLoadFlag = loadFlag;
9113 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9114 // print castling suffix
9115 if( toY == fromY && piece == king ) {
9117 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9119 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9122 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9123 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9124 boards[forwardMostMove][toY][toX] == EmptySquare
9125 && fromX != toX && fromY != toY)
9126 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9128 if(promoChar != NULLCHAR)
9129 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9131 fprintf(serverMoves, "/%d/%d",
9132 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9133 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9134 else timeLeft = blackTimeRemaining/1000;
9135 fprintf(serverMoves, "/%d", timeLeft);
9137 fflush(serverMoves);
9140 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9141 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9145 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9146 if (commentList[forwardMostMove+1] != NULL) {
9147 free(commentList[forwardMostMove+1]);
9148 commentList[forwardMostMove+1] = NULL;
9150 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9151 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9152 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9153 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9154 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9155 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9156 gameInfo.result = GameUnfinished;
9157 if (gameInfo.resultDetails != NULL) {
9158 free(gameInfo.resultDetails);
9159 gameInfo.resultDetails = NULL;
9161 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9162 moveList[forwardMostMove - 1]);
9163 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9164 PosFlags(forwardMostMove - 1),
9165 fromY, fromX, toY, toX, promoChar,
9166 parseList[forwardMostMove - 1]);
9167 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9173 if(gameInfo.variant != VariantShogi)
9174 strcat(parseList[forwardMostMove - 1], "+");
9178 strcat(parseList[forwardMostMove - 1], "#");
9181 if (appData.debugMode) {
9182 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9187 /* Updates currentMove if not pausing */
9189 ShowMove(fromX, fromY, toX, toY)
9191 int instant = (gameMode == PlayFromGameFile) ?
9192 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9193 if(appData.noGUI) return;
9194 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9196 if (forwardMostMove == currentMove + 1) {
9197 AnimateMove(boards[forwardMostMove - 1],
9198 fromX, fromY, toX, toY);
9200 if (appData.highlightLastMove) {
9201 SetHighlights(fromX, fromY, toX, toY);
9204 currentMove = forwardMostMove;
9207 if (instant) return;
9209 DisplayMove(currentMove - 1);
9210 DrawPosition(FALSE, boards[currentMove]);
9211 DisplayBothClocks();
9212 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9215 void SendEgtPath(ChessProgramState *cps)
9216 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9217 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9219 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9222 char c, *q = name+1, *r, *s;
9224 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9225 while(*p && *p != ',') *q++ = *p++;
9227 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9228 strcmp(name, ",nalimov:") == 0 ) {
9229 // take nalimov path from the menu-changeable option first, if it is defined
9230 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9231 SendToProgram(buf,cps); // send egtbpath command for nalimov
9233 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9234 (s = StrStr(appData.egtFormats, name)) != NULL) {
9235 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9236 s = r = StrStr(s, ":") + 1; // beginning of path info
9237 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9238 c = *r; *r = 0; // temporarily null-terminate path info
9239 *--q = 0; // strip of trailig ':' from name
9240 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9242 SendToProgram(buf,cps); // send egtbpath command for this format
9244 if(*p == ',') p++; // read away comma to position for next format name
9249 InitChessProgram(cps, setup)
9250 ChessProgramState *cps;
9251 int setup; /* [HGM] needed to setup FRC opening position */
9253 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9254 if (appData.noChessProgram) return;
9255 hintRequested = FALSE;
9256 bookRequested = FALSE;
9258 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9259 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9260 if(cps->memSize) { /* [HGM] memory */
9261 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9262 SendToProgram(buf, cps);
9264 SendEgtPath(cps); /* [HGM] EGT */
9265 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9266 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9267 SendToProgram(buf, cps);
9270 SendToProgram(cps->initString, cps);
9271 if (gameInfo.variant != VariantNormal &&
9272 gameInfo.variant != VariantLoadable
9273 /* [HGM] also send variant if board size non-standard */
9274 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9276 char *v = VariantName(gameInfo.variant);
9277 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9278 /* [HGM] in protocol 1 we have to assume all variants valid */
9279 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9280 DisplayFatalError(buf, 0, 1);
9284 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9285 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9286 if( gameInfo.variant == VariantXiangqi )
9287 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9288 if( gameInfo.variant == VariantShogi )
9289 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9290 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9291 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9292 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9293 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9294 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9295 if( gameInfo.variant == VariantCourier )
9296 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9297 if( gameInfo.variant == VariantSuper )
9298 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9299 if( gameInfo.variant == VariantGreat )
9300 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9301 if( gameInfo.variant == VariantSChess )
9302 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9305 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9306 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9307 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9308 if(StrStr(cps->variants, b) == NULL) {
9309 // specific sized variant not known, check if general sizing allowed
9310 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9311 if(StrStr(cps->variants, "boardsize") == NULL) {
9312 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9313 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9314 DisplayFatalError(buf, 0, 1);
9317 /* [HGM] here we really should compare with the maximum supported board size */
9320 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9321 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9322 SendToProgram(buf, cps);
9324 currentlyInitializedVariant = gameInfo.variant;
9326 /* [HGM] send opening position in FRC to first engine */
9328 SendToProgram("force\n", cps);
9330 /* engine is now in force mode! Set flag to wake it up after first move. */
9331 setboardSpoiledMachineBlack = 1;
9335 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9336 SendToProgram(buf, cps);
9338 cps->maybeThinking = FALSE;
9339 cps->offeredDraw = 0;
9340 if (!appData.icsActive) {
9341 SendTimeControl(cps, movesPerSession, timeControl,
9342 timeIncrement, appData.searchDepth,
9345 if (appData.showThinking
9346 // [HGM] thinking: four options require thinking output to be sent
9347 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9349 SendToProgram("post\n", cps);
9351 SendToProgram("hard\n", cps);
9352 if (!appData.ponderNextMove) {
9353 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9354 it without being sure what state we are in first. "hard"
9355 is not a toggle, so that one is OK.
9357 SendToProgram("easy\n", cps);
9360 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9361 SendToProgram(buf, cps);
9363 cps->initDone = TRUE;
9368 StartChessProgram(cps)
9369 ChessProgramState *cps;
9374 if (appData.noChessProgram) return;
9375 cps->initDone = FALSE;
9377 if (strcmp(cps->host, "localhost") == 0) {
9378 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9379 } else if (*appData.remoteShell == NULLCHAR) {
9380 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9382 if (*appData.remoteUser == NULLCHAR) {
9383 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9386 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9387 cps->host, appData.remoteUser, cps->program);
9389 err = StartChildProcess(buf, "", &cps->pr);
9393 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9394 DisplayFatalError(buf, err, 1);
9400 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9401 if (cps->protocolVersion > 1) {
9402 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9403 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9404 cps->comboCnt = 0; // and values of combo boxes
9405 SendToProgram(buf, cps);
9407 SendToProgram("xboard\n", cps);
9412 TwoMachinesEventIfReady P((void))
9414 static int curMess = 0;
9415 if (first.lastPing != first.lastPong) {
9416 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9417 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9420 if (second.lastPing != second.lastPong) {
9421 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9422 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9425 DisplayMessage("", ""); curMess = 0;
9431 CreateTourney(char *name)
9434 if(name[0] == NULLCHAR) return 0;
9435 f = fopen(appData.tourneyFile, "r");
9436 if(f) { // file exists
9437 ParseArgsFromFile(f); // parse it
9439 f = fopen(appData.tourneyFile, "w");
9440 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9441 // create a file with tournament description
9442 fprintf(f, "-participants {%s}\n", appData.participants);
9443 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9444 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9445 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9446 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9447 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9448 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9449 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9450 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9451 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9452 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9453 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9454 fprintf(f, "-results \"\"\n");
9458 appData.noChessProgram = FALSE;
9459 appData.clockMode = TRUE;
9464 #define MAXENGINES 1000
9465 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9467 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9469 char buf[MSG_SIZ], *p, *q;
9473 while(*p && *p != '\n') *q++ = *p++;
9475 if(engineList[i]) free(engineList[i]);
9476 engineList[i] = strdup(buf);
9478 TidyProgramName(engineList[i], "localhost", buf);
9479 if(engineMnemonic[i]) free(engineMnemonic[i]);
9480 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9482 sscanf(q + 8, "%s", buf + strlen(buf));
9485 engineMnemonic[i] = strdup(buf);
9487 if(i > MAXENGINES - 2) break;
9489 engineList[i] = NULL;
9492 // following implemented as macro to avoid type limitations
9493 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9495 void SwapEngines(int n)
9496 { // swap settings for first engine and other engine (so far only some selected options)
9501 SWAP(chessProgram, p)
9503 SWAP(hasOwnBookUCI, h)
9504 SWAP(protocolVersion, h)
9506 SWAP(scoreIsAbsolute, h)
9512 SetPlayer(int player)
9513 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9515 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9516 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9517 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9518 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9519 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9520 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9522 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9523 ParseArgsFromString(resetOptions);
9524 ParseArgsFromString(buf);
9530 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9531 { // determine players from game number
9532 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9534 if(appData.tourneyType == 0) {
9535 roundsPerCycle = (nPlayers - 1) | 1;
9536 pairingsPerRound = nPlayers / 2;
9537 } else if(appData.tourneyType > 0) {
9538 roundsPerCycle = nPlayers - appData.tourneyType;
9539 pairingsPerRound = appData.tourneyType;
9541 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9542 gamesPerCycle = gamesPerRound * roundsPerCycle;
9543 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9544 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9545 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9546 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9547 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9548 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9550 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9551 if(appData.roundSync) *syncInterval = gamesPerRound;
9553 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9555 if(appData.tourneyType == 0) {
9556 if(curPairing == (nPlayers-1)/2 ) {
9557 *whitePlayer = curRound;
9558 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9560 *whitePlayer = curRound - pairingsPerRound + curPairing;
9561 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9562 *blackPlayer = curRound + pairingsPerRound - curPairing;
9563 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9565 } else if(appData.tourneyType > 0) {
9566 *whitePlayer = curPairing;
9567 *blackPlayer = curRound + appData.tourneyType;
9570 // take care of white/black alternation per round.
9571 // For cycles and games this is already taken care of by default, derived from matchGame!
9572 return curRound & 1;
9576 NextTourneyGame(int nr, int *swapColors)
9577 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9579 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9581 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9582 tf = fopen(appData.tourneyFile, "r");
9583 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9584 ParseArgsFromFile(tf); fclose(tf);
9586 p = appData.participants;
9587 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9588 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9591 p = q = appData.results;
9592 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9593 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9594 DisplayMessage(_("Waiting for other game(s)"),"");
9595 waitingForGame = TRUE;
9596 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9599 waitingForGame = FALSE;
9602 if(first.pr != NoProc) return 1; // engines already loaded
9604 // redefine engines, engine dir, etc.
9605 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9606 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9608 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9609 SwapEngines(1); // and make that valid for second engine by swapping
9610 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9611 InitEngine(&second, 1);
9612 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9618 { // performs game initialization that does not invoke engines, and then tries to start the game
9619 int firstWhite, swapColors = 0;
9620 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9621 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9622 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9623 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9624 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9625 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9626 Reset(FALSE, first.pr != NoProc);
9627 appData.noChessProgram = FALSE;
9628 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9632 void UserAdjudicationEvent( int result )
9634 ChessMove gameResult = GameIsDrawn;
9637 gameResult = WhiteWins;
9639 else if( result < 0 ) {
9640 gameResult = BlackWins;
9643 if( gameMode == TwoMachinesPlay ) {
9644 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9649 // [HGM] save: calculate checksum of game to make games easily identifiable
9650 int StringCheckSum(char *s)
9653 if(s==NULL) return 0;
9654 while(*s) i = i*259 + *s++;
9661 for(i=backwardMostMove; i<forwardMostMove; i++) {
9662 sum += pvInfoList[i].depth;
9663 sum += StringCheckSum(parseList[i]);
9664 sum += StringCheckSum(commentList[i]);
9667 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9668 return sum + StringCheckSum(commentList[i]);
9669 } // end of save patch
9672 GameEnds(result, resultDetails, whosays)
9674 char *resultDetails;
9677 GameMode nextGameMode;
9679 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9681 if(endingGame) return; /* [HGM] crash: forbid recursion */
9683 if(twoBoards) { // [HGM] dual: switch back to one board
9684 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9685 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9687 if (appData.debugMode) {
9688 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9689 result, resultDetails ? resultDetails : "(null)", whosays);
9692 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9694 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9695 /* If we are playing on ICS, the server decides when the
9696 game is over, but the engine can offer to draw, claim
9700 if (appData.zippyPlay && first.initDone) {
9701 if (result == GameIsDrawn) {
9702 /* In case draw still needs to be claimed */
9703 SendToICS(ics_prefix);
9704 SendToICS("draw\n");
9705 } else if (StrCaseStr(resultDetails, "resign")) {
9706 SendToICS(ics_prefix);
9707 SendToICS("resign\n");
9711 endingGame = 0; /* [HGM] crash */
9715 /* If we're loading the game from a file, stop */
9716 if (whosays == GE_FILE) {
9717 (void) StopLoadGameTimer();
9721 /* Cancel draw offers */
9722 first.offeredDraw = second.offeredDraw = 0;
9724 /* If this is an ICS game, only ICS can really say it's done;
9725 if not, anyone can. */
9726 isIcsGame = (gameMode == IcsPlayingWhite ||
9727 gameMode == IcsPlayingBlack ||
9728 gameMode == IcsObserving ||
9729 gameMode == IcsExamining);
9731 if (!isIcsGame || whosays == GE_ICS) {
9732 /* OK -- not an ICS game, or ICS said it was done */
9734 if (!isIcsGame && !appData.noChessProgram)
9735 SetUserThinkingEnables();
9737 /* [HGM] if a machine claims the game end we verify this claim */
9738 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9739 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9741 ChessMove trueResult = (ChessMove) -1;
9743 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9744 first.twoMachinesColor[0] :
9745 second.twoMachinesColor[0] ;
9747 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9748 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9749 /* [HGM] verify: engine mate claims accepted if they were flagged */
9750 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9752 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9753 /* [HGM] verify: engine mate claims accepted if they were flagged */
9754 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9756 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9757 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9760 // now verify win claims, but not in drop games, as we don't understand those yet
9761 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9762 || gameInfo.variant == VariantGreat) &&
9763 (result == WhiteWins && claimer == 'w' ||
9764 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9765 if (appData.debugMode) {
9766 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9767 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9769 if(result != trueResult) {
9770 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9771 result = claimer == 'w' ? BlackWins : WhiteWins;
9772 resultDetails = buf;
9775 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9776 && (forwardMostMove <= backwardMostMove ||
9777 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9778 (claimer=='b')==(forwardMostMove&1))
9780 /* [HGM] verify: draws that were not flagged are false claims */
9781 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9782 result = claimer == 'w' ? BlackWins : WhiteWins;
9783 resultDetails = buf;
9785 /* (Claiming a loss is accepted no questions asked!) */
9787 /* [HGM] bare: don't allow bare King to win */
9788 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9789 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9790 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9791 && result != GameIsDrawn)
9792 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9793 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9794 int p = (signed char)boards[forwardMostMove][i][j] - color;
9795 if(p >= 0 && p <= (int)WhiteKing) k++;
9797 if (appData.debugMode) {
9798 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9799 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9802 result = GameIsDrawn;
9803 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9804 resultDetails = buf;
9810 if(serverMoves != NULL && !loadFlag) { char c = '=';
9811 if(result==WhiteWins) c = '+';
9812 if(result==BlackWins) c = '-';
9813 if(resultDetails != NULL)
9814 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9816 if (resultDetails != NULL) {
9817 gameInfo.result = result;
9818 gameInfo.resultDetails = StrSave(resultDetails);
9820 /* display last move only if game was not loaded from file */
9821 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9822 DisplayMove(currentMove - 1);
9824 if (forwardMostMove != 0) {
9825 if (gameMode != PlayFromGameFile && gameMode != EditGame
9826 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9828 if (*appData.saveGameFile != NULLCHAR) {
9829 SaveGameToFile(appData.saveGameFile, TRUE);
9830 } else if (appData.autoSaveGames) {
9833 if (*appData.savePositionFile != NULLCHAR) {
9834 SavePositionToFile(appData.savePositionFile);
9839 /* Tell program how game ended in case it is learning */
9840 /* [HGM] Moved this to after saving the PGN, just in case */
9841 /* engine died and we got here through time loss. In that */
9842 /* case we will get a fatal error writing the pipe, which */
9843 /* would otherwise lose us the PGN. */
9844 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9845 /* output during GameEnds should never be fatal anymore */
9846 if (gameMode == MachinePlaysWhite ||
9847 gameMode == MachinePlaysBlack ||
9848 gameMode == TwoMachinesPlay ||
9849 gameMode == IcsPlayingWhite ||
9850 gameMode == IcsPlayingBlack ||
9851 gameMode == BeginningOfGame) {
9853 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9855 if (first.pr != NoProc) {
9856 SendToProgram(buf, &first);
9858 if (second.pr != NoProc &&
9859 gameMode == TwoMachinesPlay) {
9860 SendToProgram(buf, &second);
9865 if (appData.icsActive) {
9866 if (appData.quietPlay &&
9867 (gameMode == IcsPlayingWhite ||
9868 gameMode == IcsPlayingBlack)) {
9869 SendToICS(ics_prefix);
9870 SendToICS("set shout 1\n");
9872 nextGameMode = IcsIdle;
9873 ics_user_moved = FALSE;
9874 /* clean up premove. It's ugly when the game has ended and the
9875 * premove highlights are still on the board.
9879 ClearPremoveHighlights();
9880 DrawPosition(FALSE, boards[currentMove]);
9882 if (whosays == GE_ICS) {
9885 if (gameMode == IcsPlayingWhite)
9887 else if(gameMode == IcsPlayingBlack)
9891 if (gameMode == IcsPlayingBlack)
9893 else if(gameMode == IcsPlayingWhite)
9900 PlayIcsUnfinishedSound();
9903 } else if (gameMode == EditGame ||
9904 gameMode == PlayFromGameFile ||
9905 gameMode == AnalyzeMode ||
9906 gameMode == AnalyzeFile) {
9907 nextGameMode = gameMode;
9909 nextGameMode = EndOfGame;
9914 nextGameMode = gameMode;
9917 if (appData.noChessProgram) {
9918 gameMode = nextGameMode;
9920 endingGame = 0; /* [HGM] crash */
9925 /* Put first chess program into idle state */
9926 if (first.pr != NoProc &&
9927 (gameMode == MachinePlaysWhite ||
9928 gameMode == MachinePlaysBlack ||
9929 gameMode == TwoMachinesPlay ||
9930 gameMode == IcsPlayingWhite ||
9931 gameMode == IcsPlayingBlack ||
9932 gameMode == BeginningOfGame)) {
9933 SendToProgram("force\n", &first);
9934 if (first.usePing) {
9936 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9937 SendToProgram(buf, &first);
9940 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9941 /* Kill off first chess program */
9942 if (first.isr != NULL)
9943 RemoveInputSource(first.isr);
9946 if (first.pr != NoProc) {
9948 DoSleep( appData.delayBeforeQuit );
9949 SendToProgram("quit\n", &first);
9950 DoSleep( appData.delayAfterQuit );
9951 DestroyChildProcess(first.pr, first.useSigterm);
9956 /* Put second chess program into idle state */
9957 if (second.pr != NoProc &&
9958 gameMode == TwoMachinesPlay) {
9959 SendToProgram("force\n", &second);
9960 if (second.usePing) {
9962 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9963 SendToProgram(buf, &second);
9966 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9967 /* Kill off second chess program */
9968 if (second.isr != NULL)
9969 RemoveInputSource(second.isr);
9972 if (second.pr != NoProc) {
9973 DoSleep( appData.delayBeforeQuit );
9974 SendToProgram("quit\n", &second);
9975 DoSleep( appData.delayAfterQuit );
9976 DestroyChildProcess(second.pr, second.useSigterm);
9981 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9986 if (first.twoMachinesColor[0] == 'w') {
9994 if (first.twoMachinesColor[0] == 'b') {
10000 case GameUnfinished:
10006 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10007 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10008 ReserveGame(nextGame, resChar); // sets nextGame
10009 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10010 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10012 if (nextGame <= appData.matchGames) {
10013 gameMode = nextGameMode;
10014 matchGame = nextGame; // this will be overruled in tourney mode!
10015 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10016 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10017 endingGame = 0; /* [HGM] crash */
10020 gameMode = nextGameMode;
10021 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10022 first.tidy, second.tidy,
10023 first.matchWins, second.matchWins,
10024 appData.matchGames - (first.matchWins + second.matchWins));
10025 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10026 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10027 first.twoMachinesColor = "black\n";
10028 second.twoMachinesColor = "white\n";
10030 first.twoMachinesColor = "white\n";
10031 second.twoMachinesColor = "black\n";
10035 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10036 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10038 gameMode = nextGameMode;
10040 endingGame = 0; /* [HGM] crash */
10041 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10042 if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10043 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10044 DisplayNote(ranking ? ranking : buf);
10046 if(ranking) free(ranking);
10050 /* Assumes program was just initialized (initString sent).
10051 Leaves program in force mode. */
10053 FeedMovesToProgram(cps, upto)
10054 ChessProgramState *cps;
10059 if (appData.debugMode)
10060 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10061 startedFromSetupPosition ? "position and " : "",
10062 backwardMostMove, upto, cps->which);
10063 if(currentlyInitializedVariant != gameInfo.variant) {
10065 // [HGM] variantswitch: make engine aware of new variant
10066 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10067 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10068 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10069 SendToProgram(buf, cps);
10070 currentlyInitializedVariant = gameInfo.variant;
10072 SendToProgram("force\n", cps);
10073 if (startedFromSetupPosition) {
10074 SendBoard(cps, backwardMostMove);
10075 if (appData.debugMode) {
10076 fprintf(debugFP, "feedMoves\n");
10079 for (i = backwardMostMove; i < upto; i++) {
10080 SendMoveToProgram(i, cps);
10086 ResurrectChessProgram()
10088 /* The chess program may have exited.
10089 If so, restart it and feed it all the moves made so far. */
10090 static int doInit = 0;
10092 if (appData.noChessProgram) return 1;
10094 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10095 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10096 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10097 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10099 if (first.pr != NoProc) return 1;
10100 StartChessProgram(&first);
10102 InitChessProgram(&first, FALSE);
10103 FeedMovesToProgram(&first, currentMove);
10105 if (!first.sendTime) {
10106 /* can't tell gnuchess what its clock should read,
10107 so we bow to its notion. */
10109 timeRemaining[0][currentMove] = whiteTimeRemaining;
10110 timeRemaining[1][currentMove] = blackTimeRemaining;
10113 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10114 appData.icsEngineAnalyze) && first.analysisSupport) {
10115 SendToProgram("analyze\n", &first);
10116 first.analyzing = TRUE;
10122 * Button procedures
10125 Reset(redraw, init)
10130 if (appData.debugMode) {
10131 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10132 redraw, init, gameMode);
10134 CleanupTail(); // [HGM] vari: delete any stored variations
10135 pausing = pauseExamInvalid = FALSE;
10136 startedFromSetupPosition = blackPlaysFirst = FALSE;
10138 whiteFlag = blackFlag = FALSE;
10139 userOfferedDraw = FALSE;
10140 hintRequested = bookRequested = FALSE;
10141 first.maybeThinking = FALSE;
10142 second.maybeThinking = FALSE;
10143 first.bookSuspend = FALSE; // [HGM] book
10144 second.bookSuspend = FALSE;
10145 thinkOutput[0] = NULLCHAR;
10146 lastHint[0] = NULLCHAR;
10147 ClearGameInfo(&gameInfo);
10148 gameInfo.variant = StringToVariant(appData.variant);
10149 ics_user_moved = ics_clock_paused = FALSE;
10150 ics_getting_history = H_FALSE;
10152 white_holding[0] = black_holding[0] = NULLCHAR;
10153 ClearProgramStats();
10154 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10158 flipView = appData.flipView;
10159 ClearPremoveHighlights();
10160 gotPremove = FALSE;
10161 alarmSounded = FALSE;
10163 GameEnds(EndOfFile, NULL, GE_PLAYER);
10164 if(appData.serverMovesName != NULL) {
10165 /* [HGM] prepare to make moves file for broadcasting */
10166 clock_t t = clock();
10167 if(serverMoves != NULL) fclose(serverMoves);
10168 serverMoves = fopen(appData.serverMovesName, "r");
10169 if(serverMoves != NULL) {
10170 fclose(serverMoves);
10171 /* delay 15 sec before overwriting, so all clients can see end */
10172 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10174 serverMoves = fopen(appData.serverMovesName, "w");
10178 gameMode = BeginningOfGame;
10180 if(appData.icsActive) gameInfo.variant = VariantNormal;
10181 currentMove = forwardMostMove = backwardMostMove = 0;
10182 InitPosition(redraw);
10183 for (i = 0; i < MAX_MOVES; i++) {
10184 if (commentList[i] != NULL) {
10185 free(commentList[i]);
10186 commentList[i] = NULL;
10190 timeRemaining[0][0] = whiteTimeRemaining;
10191 timeRemaining[1][0] = blackTimeRemaining;
10193 if (first.pr == NULL) {
10194 StartChessProgram(&first);
10197 InitChessProgram(&first, startedFromSetupPosition);
10200 DisplayMessage("", "");
10201 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10202 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10209 if (!AutoPlayOneMove())
10211 if (matchMode || appData.timeDelay == 0)
10213 if (appData.timeDelay < 0)
10215 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10224 int fromX, fromY, toX, toY;
10226 if (appData.debugMode) {
10227 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10230 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10233 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10234 pvInfoList[currentMove].depth = programStats.depth;
10235 pvInfoList[currentMove].score = programStats.score;
10236 pvInfoList[currentMove].time = 0;
10237 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10240 if (currentMove >= forwardMostMove) {
10241 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10242 gameMode = EditGame;
10245 /* [AS] Clear current move marker at the end of a game */
10246 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10251 toX = moveList[currentMove][2] - AAA;
10252 toY = moveList[currentMove][3] - ONE;
10254 if (moveList[currentMove][1] == '@') {
10255 if (appData.highlightLastMove) {
10256 SetHighlights(-1, -1, toX, toY);
10259 fromX = moveList[currentMove][0] - AAA;
10260 fromY = moveList[currentMove][1] - ONE;
10262 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10264 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10266 if (appData.highlightLastMove) {
10267 SetHighlights(fromX, fromY, toX, toY);
10270 DisplayMove(currentMove);
10271 SendMoveToProgram(currentMove++, &first);
10272 DisplayBothClocks();
10273 DrawPosition(FALSE, boards[currentMove]);
10274 // [HGM] PV info: always display, routine tests if empty
10275 DisplayComment(currentMove - 1, commentList[currentMove]);
10281 LoadGameOneMove(readAhead)
10282 ChessMove readAhead;
10284 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10285 char promoChar = NULLCHAR;
10286 ChessMove moveType;
10287 char move[MSG_SIZ];
10290 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10291 gameMode != AnalyzeMode && gameMode != Training) {
10296 yyboardindex = forwardMostMove;
10297 if (readAhead != EndOfFile) {
10298 moveType = readAhead;
10300 if (gameFileFP == NULL)
10302 moveType = (ChessMove) Myylex();
10306 switch (moveType) {
10308 if (appData.debugMode)
10309 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10312 /* append the comment but don't display it */
10313 AppendComment(currentMove, p, FALSE);
10316 case WhiteCapturesEnPassant:
10317 case BlackCapturesEnPassant:
10318 case WhitePromotion:
10319 case BlackPromotion:
10320 case WhiteNonPromotion:
10321 case BlackNonPromotion:
10323 case WhiteKingSideCastle:
10324 case WhiteQueenSideCastle:
10325 case BlackKingSideCastle:
10326 case BlackQueenSideCastle:
10327 case WhiteKingSideCastleWild:
10328 case WhiteQueenSideCastleWild:
10329 case BlackKingSideCastleWild:
10330 case BlackQueenSideCastleWild:
10332 case WhiteHSideCastleFR:
10333 case WhiteASideCastleFR:
10334 case BlackHSideCastleFR:
10335 case BlackASideCastleFR:
10337 if (appData.debugMode)
10338 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10339 fromX = currentMoveString[0] - AAA;
10340 fromY = currentMoveString[1] - ONE;
10341 toX = currentMoveString[2] - AAA;
10342 toY = currentMoveString[3] - ONE;
10343 promoChar = currentMoveString[4];
10348 if (appData.debugMode)
10349 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10350 fromX = moveType == WhiteDrop ?
10351 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10352 (int) CharToPiece(ToLower(currentMoveString[0]));
10354 toX = currentMoveString[2] - AAA;
10355 toY = currentMoveString[3] - ONE;
10361 case GameUnfinished:
10362 if (appData.debugMode)
10363 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10364 p = strchr(yy_text, '{');
10365 if (p == NULL) p = strchr(yy_text, '(');
10368 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10370 q = strchr(p, *p == '{' ? '}' : ')');
10371 if (q != NULL) *q = NULLCHAR;
10374 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10375 GameEnds(moveType, p, GE_FILE);
10377 if (cmailMsgLoaded) {
10379 flipView = WhiteOnMove(currentMove);
10380 if (moveType == GameUnfinished) flipView = !flipView;
10381 if (appData.debugMode)
10382 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10387 if (appData.debugMode)
10388 fprintf(debugFP, "Parser hit end of file\n");
10389 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10395 if (WhiteOnMove(currentMove)) {
10396 GameEnds(BlackWins, "Black mates", GE_FILE);
10398 GameEnds(WhiteWins, "White mates", GE_FILE);
10402 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10408 case MoveNumberOne:
10409 if (lastLoadGameStart == GNUChessGame) {
10410 /* GNUChessGames have numbers, but they aren't move numbers */
10411 if (appData.debugMode)
10412 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10413 yy_text, (int) moveType);
10414 return LoadGameOneMove(EndOfFile); /* tail recursion */
10416 /* else fall thru */
10421 /* Reached start of next game in file */
10422 if (appData.debugMode)
10423 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10424 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10430 if (WhiteOnMove(currentMove)) {
10431 GameEnds(BlackWins, "Black mates", GE_FILE);
10433 GameEnds(WhiteWins, "White mates", GE_FILE);
10437 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10443 case PositionDiagram: /* should not happen; ignore */
10444 case ElapsedTime: /* ignore */
10445 case NAG: /* ignore */
10446 if (appData.debugMode)
10447 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10448 yy_text, (int) moveType);
10449 return LoadGameOneMove(EndOfFile); /* tail recursion */
10452 if (appData.testLegality) {
10453 if (appData.debugMode)
10454 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10455 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10456 (forwardMostMove / 2) + 1,
10457 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10458 DisplayError(move, 0);
10461 if (appData.debugMode)
10462 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10463 yy_text, currentMoveString);
10464 fromX = currentMoveString[0] - AAA;
10465 fromY = currentMoveString[1] - ONE;
10466 toX = currentMoveString[2] - AAA;
10467 toY = currentMoveString[3] - ONE;
10468 promoChar = currentMoveString[4];
10472 case AmbiguousMove:
10473 if (appData.debugMode)
10474 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10475 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10476 (forwardMostMove / 2) + 1,
10477 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10478 DisplayError(move, 0);
10483 case ImpossibleMove:
10484 if (appData.debugMode)
10485 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10486 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10487 (forwardMostMove / 2) + 1,
10488 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10489 DisplayError(move, 0);
10495 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10496 DrawPosition(FALSE, boards[currentMove]);
10497 DisplayBothClocks();
10498 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10499 DisplayComment(currentMove - 1, commentList[currentMove]);
10501 (void) StopLoadGameTimer();
10503 cmailOldMove = forwardMostMove;
10506 /* currentMoveString is set as a side-effect of yylex */
10508 thinkOutput[0] = NULLCHAR;
10509 MakeMove(fromX, fromY, toX, toY, promoChar);
10510 currentMove = forwardMostMove;
10515 /* Load the nth game from the given file */
10517 LoadGameFromFile(filename, n, title, useList)
10521 /*Boolean*/ int useList;
10526 if (strcmp(filename, "-") == 0) {
10530 f = fopen(filename, "rb");
10532 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10533 DisplayError(buf, errno);
10537 if (fseek(f, 0, 0) == -1) {
10538 /* f is not seekable; probably a pipe */
10541 if (useList && n == 0) {
10542 int error = GameListBuild(f);
10544 DisplayError(_("Cannot build game list"), error);
10545 } else if (!ListEmpty(&gameList) &&
10546 ((ListGame *) gameList.tailPred)->number > 1) {
10547 GameListPopUp(f, title);
10554 return LoadGame(f, n, title, FALSE);
10559 MakeRegisteredMove()
10561 int fromX, fromY, toX, toY;
10563 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10564 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10567 if (appData.debugMode)
10568 fprintf(debugFP, "Restoring %s for game %d\n",
10569 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10571 thinkOutput[0] = NULLCHAR;
10572 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10573 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10574 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10575 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10576 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10577 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10578 MakeMove(fromX, fromY, toX, toY, promoChar);
10579 ShowMove(fromX, fromY, toX, toY);
10581 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10588 if (WhiteOnMove(currentMove)) {
10589 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10591 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10596 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10603 if (WhiteOnMove(currentMove)) {
10604 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10606 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10611 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10622 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10624 CmailLoadGame(f, gameNumber, title, useList)
10632 if (gameNumber > nCmailGames) {
10633 DisplayError(_("No more games in this message"), 0);
10636 if (f == lastLoadGameFP) {
10637 int offset = gameNumber - lastLoadGameNumber;
10639 cmailMsg[0] = NULLCHAR;
10640 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10641 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10642 nCmailMovesRegistered--;
10644 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10645 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10646 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10649 if (! RegisterMove()) return FALSE;
10653 retVal = LoadGame(f, gameNumber, title, useList);
10655 /* Make move registered during previous look at this game, if any */
10656 MakeRegisteredMove();
10658 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10659 commentList[currentMove]
10660 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10661 DisplayComment(currentMove - 1, commentList[currentMove]);
10667 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10672 int gameNumber = lastLoadGameNumber + offset;
10673 if (lastLoadGameFP == NULL) {
10674 DisplayError(_("No game has been loaded yet"), 0);
10677 if (gameNumber <= 0) {
10678 DisplayError(_("Can't back up any further"), 0);
10681 if (cmailMsgLoaded) {
10682 return CmailLoadGame(lastLoadGameFP, gameNumber,
10683 lastLoadGameTitle, lastLoadGameUseList);
10685 return LoadGame(lastLoadGameFP, gameNumber,
10686 lastLoadGameTitle, lastLoadGameUseList);
10692 /* Load the nth game from open file f */
10694 LoadGame(f, gameNumber, title, useList)
10702 int gn = gameNumber;
10703 ListGame *lg = NULL;
10704 int numPGNTags = 0;
10706 GameMode oldGameMode;
10707 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10709 if (appData.debugMode)
10710 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10712 if (gameMode == Training )
10713 SetTrainingModeOff();
10715 oldGameMode = gameMode;
10716 if (gameMode != BeginningOfGame) {
10717 Reset(FALSE, TRUE);
10721 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10722 fclose(lastLoadGameFP);
10726 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10729 fseek(f, lg->offset, 0);
10730 GameListHighlight(gameNumber);
10734 DisplayError(_("Game number out of range"), 0);
10739 if (fseek(f, 0, 0) == -1) {
10740 if (f == lastLoadGameFP ?
10741 gameNumber == lastLoadGameNumber + 1 :
10745 DisplayError(_("Can't seek on game file"), 0);
10750 lastLoadGameFP = f;
10751 lastLoadGameNumber = gameNumber;
10752 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10753 lastLoadGameUseList = useList;
10757 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10758 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10759 lg->gameInfo.black);
10761 } else if (*title != NULLCHAR) {
10762 if (gameNumber > 1) {
10763 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10766 DisplayTitle(title);
10770 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10771 gameMode = PlayFromGameFile;
10775 currentMove = forwardMostMove = backwardMostMove = 0;
10776 CopyBoard(boards[0], initialPosition);
10780 * Skip the first gn-1 games in the file.
10781 * Also skip over anything that precedes an identifiable
10782 * start of game marker, to avoid being confused by
10783 * garbage at the start of the file. Currently
10784 * recognized start of game markers are the move number "1",
10785 * the pattern "gnuchess .* game", the pattern
10786 * "^[#;%] [^ ]* game file", and a PGN tag block.
10787 * A game that starts with one of the latter two patterns
10788 * will also have a move number 1, possibly
10789 * following a position diagram.
10790 * 5-4-02: Let's try being more lenient and allowing a game to
10791 * start with an unnumbered move. Does that break anything?
10793 cm = lastLoadGameStart = EndOfFile;
10795 yyboardindex = forwardMostMove;
10796 cm = (ChessMove) Myylex();
10799 if (cmailMsgLoaded) {
10800 nCmailGames = CMAIL_MAX_GAMES - gn;
10803 DisplayError(_("Game not found in file"), 0);
10810 lastLoadGameStart = cm;
10813 case MoveNumberOne:
10814 switch (lastLoadGameStart) {
10819 case MoveNumberOne:
10821 gn--; /* count this game */
10822 lastLoadGameStart = cm;
10831 switch (lastLoadGameStart) {
10834 case MoveNumberOne:
10836 gn--; /* count this game */
10837 lastLoadGameStart = cm;
10840 lastLoadGameStart = cm; /* game counted already */
10848 yyboardindex = forwardMostMove;
10849 cm = (ChessMove) Myylex();
10850 } while (cm == PGNTag || cm == Comment);
10857 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10858 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10859 != CMAIL_OLD_RESULT) {
10861 cmailResult[ CMAIL_MAX_GAMES
10862 - gn - 1] = CMAIL_OLD_RESULT;
10868 /* Only a NormalMove can be at the start of a game
10869 * without a position diagram. */
10870 if (lastLoadGameStart == EndOfFile ) {
10872 lastLoadGameStart = MoveNumberOne;
10881 if (appData.debugMode)
10882 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10884 if (cm == XBoardGame) {
10885 /* Skip any header junk before position diagram and/or move 1 */
10887 yyboardindex = forwardMostMove;
10888 cm = (ChessMove) Myylex();
10890 if (cm == EndOfFile ||
10891 cm == GNUChessGame || cm == XBoardGame) {
10892 /* Empty game; pretend end-of-file and handle later */
10897 if (cm == MoveNumberOne || cm == PositionDiagram ||
10898 cm == PGNTag || cm == Comment)
10901 } else if (cm == GNUChessGame) {
10902 if (gameInfo.event != NULL) {
10903 free(gameInfo.event);
10905 gameInfo.event = StrSave(yy_text);
10908 startedFromSetupPosition = FALSE;
10909 while (cm == PGNTag) {
10910 if (appData.debugMode)
10911 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10912 err = ParsePGNTag(yy_text, &gameInfo);
10913 if (!err) numPGNTags++;
10915 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10916 if(gameInfo.variant != oldVariant) {
10917 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10918 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10919 InitPosition(TRUE);
10920 oldVariant = gameInfo.variant;
10921 if (appData.debugMode)
10922 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10926 if (gameInfo.fen != NULL) {
10927 Board initial_position;
10928 startedFromSetupPosition = TRUE;
10929 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10931 DisplayError(_("Bad FEN position in file"), 0);
10934 CopyBoard(boards[0], initial_position);
10935 if (blackPlaysFirst) {
10936 currentMove = forwardMostMove = backwardMostMove = 1;
10937 CopyBoard(boards[1], initial_position);
10938 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10939 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10940 timeRemaining[0][1] = whiteTimeRemaining;
10941 timeRemaining[1][1] = blackTimeRemaining;
10942 if (commentList[0] != NULL) {
10943 commentList[1] = commentList[0];
10944 commentList[0] = NULL;
10947 currentMove = forwardMostMove = backwardMostMove = 0;
10949 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10951 initialRulePlies = FENrulePlies;
10952 for( i=0; i< nrCastlingRights; i++ )
10953 initialRights[i] = initial_position[CASTLING][i];
10955 yyboardindex = forwardMostMove;
10956 free(gameInfo.fen);
10957 gameInfo.fen = NULL;
10960 yyboardindex = forwardMostMove;
10961 cm = (ChessMove) Myylex();
10963 /* Handle comments interspersed among the tags */
10964 while (cm == Comment) {
10966 if (appData.debugMode)
10967 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10969 AppendComment(currentMove, p, FALSE);
10970 yyboardindex = forwardMostMove;
10971 cm = (ChessMove) Myylex();
10975 /* don't rely on existence of Event tag since if game was
10976 * pasted from clipboard the Event tag may not exist
10978 if (numPGNTags > 0){
10980 if (gameInfo.variant == VariantNormal) {
10981 VariantClass v = StringToVariant(gameInfo.event);
10982 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10983 if(v < VariantShogi) gameInfo.variant = v;
10986 if( appData.autoDisplayTags ) {
10987 tags = PGNTags(&gameInfo);
10988 TagsPopUp(tags, CmailMsg());
10993 /* Make something up, but don't display it now */
10998 if (cm == PositionDiagram) {
11001 Board initial_position;
11003 if (appData.debugMode)
11004 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11006 if (!startedFromSetupPosition) {
11008 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11009 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11020 initial_position[i][j++] = CharToPiece(*p);
11023 while (*p == ' ' || *p == '\t' ||
11024 *p == '\n' || *p == '\r') p++;
11026 if (strncmp(p, "black", strlen("black"))==0)
11027 blackPlaysFirst = TRUE;
11029 blackPlaysFirst = FALSE;
11030 startedFromSetupPosition = TRUE;
11032 CopyBoard(boards[0], initial_position);
11033 if (blackPlaysFirst) {
11034 currentMove = forwardMostMove = backwardMostMove = 1;
11035 CopyBoard(boards[1], initial_position);
11036 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11037 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11038 timeRemaining[0][1] = whiteTimeRemaining;
11039 timeRemaining[1][1] = blackTimeRemaining;
11040 if (commentList[0] != NULL) {
11041 commentList[1] = commentList[0];
11042 commentList[0] = NULL;
11045 currentMove = forwardMostMove = backwardMostMove = 0;
11048 yyboardindex = forwardMostMove;
11049 cm = (ChessMove) Myylex();
11052 if (first.pr == NoProc) {
11053 StartChessProgram(&first);
11055 InitChessProgram(&first, FALSE);
11056 SendToProgram("force\n", &first);
11057 if (startedFromSetupPosition) {
11058 SendBoard(&first, forwardMostMove);
11059 if (appData.debugMode) {
11060 fprintf(debugFP, "Load Game\n");
11062 DisplayBothClocks();
11065 /* [HGM] server: flag to write setup moves in broadcast file as one */
11066 loadFlag = appData.suppressLoadMoves;
11068 while (cm == Comment) {
11070 if (appData.debugMode)
11071 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11073 AppendComment(currentMove, p, FALSE);
11074 yyboardindex = forwardMostMove;
11075 cm = (ChessMove) Myylex();
11078 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11079 cm == WhiteWins || cm == BlackWins ||
11080 cm == GameIsDrawn || cm == GameUnfinished) {
11081 DisplayMessage("", _("No moves in game"));
11082 if (cmailMsgLoaded) {
11083 if (appData.debugMode)
11084 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11088 DrawPosition(FALSE, boards[currentMove]);
11089 DisplayBothClocks();
11090 gameMode = EditGame;
11097 // [HGM] PV info: routine tests if comment empty
11098 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11099 DisplayComment(currentMove - 1, commentList[currentMove]);
11101 if (!matchMode && appData.timeDelay != 0)
11102 DrawPosition(FALSE, boards[currentMove]);
11104 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11105 programStats.ok_to_send = 1;
11108 /* if the first token after the PGN tags is a move
11109 * and not move number 1, retrieve it from the parser
11111 if (cm != MoveNumberOne)
11112 LoadGameOneMove(cm);
11114 /* load the remaining moves from the file */
11115 while (LoadGameOneMove(EndOfFile)) {
11116 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11117 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11120 /* rewind to the start of the game */
11121 currentMove = backwardMostMove;
11123 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11125 if (oldGameMode == AnalyzeFile ||
11126 oldGameMode == AnalyzeMode) {
11127 AnalyzeFileEvent();
11130 if (matchMode || appData.timeDelay == 0) {
11132 gameMode = EditGame;
11134 } else if (appData.timeDelay > 0) {
11135 AutoPlayGameLoop();
11138 if (appData.debugMode)
11139 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11141 loadFlag = 0; /* [HGM] true game starts */
11145 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11147 ReloadPosition(offset)
11150 int positionNumber = lastLoadPositionNumber + offset;
11151 if (lastLoadPositionFP == NULL) {
11152 DisplayError(_("No position has been loaded yet"), 0);
11155 if (positionNumber <= 0) {
11156 DisplayError(_("Can't back up any further"), 0);
11159 return LoadPosition(lastLoadPositionFP, positionNumber,
11160 lastLoadPositionTitle);
11163 /* Load the nth position from the given file */
11165 LoadPositionFromFile(filename, n, title)
11173 if (strcmp(filename, "-") == 0) {
11174 return LoadPosition(stdin, n, "stdin");
11176 f = fopen(filename, "rb");
11178 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11179 DisplayError(buf, errno);
11182 return LoadPosition(f, n, title);
11187 /* Load the nth position from the given open file, and close it */
11189 LoadPosition(f, positionNumber, title)
11191 int positionNumber;
11194 char *p, line[MSG_SIZ];
11195 Board initial_position;
11196 int i, j, fenMode, pn;
11198 if (gameMode == Training )
11199 SetTrainingModeOff();
11201 if (gameMode != BeginningOfGame) {
11202 Reset(FALSE, TRUE);
11204 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11205 fclose(lastLoadPositionFP);
11207 if (positionNumber == 0) positionNumber = 1;
11208 lastLoadPositionFP = f;
11209 lastLoadPositionNumber = positionNumber;
11210 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11211 if (first.pr == NoProc) {
11212 StartChessProgram(&first);
11213 InitChessProgram(&first, FALSE);
11215 pn = positionNumber;
11216 if (positionNumber < 0) {
11217 /* Negative position number means to seek to that byte offset */
11218 if (fseek(f, -positionNumber, 0) == -1) {
11219 DisplayError(_("Can't seek on position file"), 0);
11224 if (fseek(f, 0, 0) == -1) {
11225 if (f == lastLoadPositionFP ?
11226 positionNumber == lastLoadPositionNumber + 1 :
11227 positionNumber == 1) {
11230 DisplayError(_("Can't seek on position file"), 0);
11235 /* See if this file is FEN or old-style xboard */
11236 if (fgets(line, MSG_SIZ, f) == NULL) {
11237 DisplayError(_("Position not found in file"), 0);
11240 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11241 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11244 if (fenMode || line[0] == '#') pn--;
11246 /* skip positions before number pn */
11247 if (fgets(line, MSG_SIZ, f) == NULL) {
11249 DisplayError(_("Position not found in file"), 0);
11252 if (fenMode || line[0] == '#') pn--;
11257 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11258 DisplayError(_("Bad FEN position in file"), 0);
11262 (void) fgets(line, MSG_SIZ, f);
11263 (void) fgets(line, MSG_SIZ, f);
11265 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11266 (void) fgets(line, MSG_SIZ, f);
11267 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11270 initial_position[i][j++] = CharToPiece(*p);
11274 blackPlaysFirst = FALSE;
11276 (void) fgets(line, MSG_SIZ, f);
11277 if (strncmp(line, "black", strlen("black"))==0)
11278 blackPlaysFirst = TRUE;
11281 startedFromSetupPosition = TRUE;
11283 SendToProgram("force\n", &first);
11284 CopyBoard(boards[0], initial_position);
11285 if (blackPlaysFirst) {
11286 currentMove = forwardMostMove = backwardMostMove = 1;
11287 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11288 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11289 CopyBoard(boards[1], initial_position);
11290 DisplayMessage("", _("Black to play"));
11292 currentMove = forwardMostMove = backwardMostMove = 0;
11293 DisplayMessage("", _("White to play"));
11295 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11296 SendBoard(&first, forwardMostMove);
11297 if (appData.debugMode) {
11299 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11300 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11301 fprintf(debugFP, "Load Position\n");
11304 if (positionNumber > 1) {
11305 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11306 DisplayTitle(line);
11308 DisplayTitle(title);
11310 gameMode = EditGame;
11313 timeRemaining[0][1] = whiteTimeRemaining;
11314 timeRemaining[1][1] = blackTimeRemaining;
11315 DrawPosition(FALSE, boards[currentMove]);
11322 CopyPlayerNameIntoFileName(dest, src)
11325 while (*src != NULLCHAR && *src != ',') {
11330 *(*dest)++ = *src++;
11335 char *DefaultFileName(ext)
11338 static char def[MSG_SIZ];
11341 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11343 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11345 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11347 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11354 /* Save the current game to the given file */
11356 SaveGameToFile(filename, append)
11364 if (strcmp(filename, "-") == 0) {
11365 return SaveGame(stdout, 0, NULL);
11367 f = fopen(filename, append ? "a" : "w");
11369 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11370 DisplayError(buf, errno);
11373 safeStrCpy(buf, lastMsg, MSG_SIZ);
11374 DisplayMessage(_("Waiting for access to save file"), "");
11375 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11376 DisplayMessage(_("Saving game"), "");
11377 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11378 result = SaveGame(f, 0, NULL);
11379 DisplayMessage(buf, "");
11389 static char buf[MSG_SIZ];
11392 p = strchr(str, ' ');
11393 if (p == NULL) return str;
11394 strncpy(buf, str, p - str);
11395 buf[p - str] = NULLCHAR;
11399 #define PGN_MAX_LINE 75
11401 #define PGN_SIDE_WHITE 0
11402 #define PGN_SIDE_BLACK 1
11405 static int FindFirstMoveOutOfBook( int side )
11409 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11410 int index = backwardMostMove;
11411 int has_book_hit = 0;
11413 if( (index % 2) != side ) {
11417 while( index < forwardMostMove ) {
11418 /* Check to see if engine is in book */
11419 int depth = pvInfoList[index].depth;
11420 int score = pvInfoList[index].score;
11426 else if( score == 0 && depth == 63 ) {
11427 in_book = 1; /* Zappa */
11429 else if( score == 2 && depth == 99 ) {
11430 in_book = 1; /* Abrok */
11433 has_book_hit += in_book;
11449 void GetOutOfBookInfo( char * buf )
11453 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11455 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11456 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11460 if( oob[0] >= 0 || oob[1] >= 0 ) {
11461 for( i=0; i<2; i++ ) {
11465 if( i > 0 && oob[0] >= 0 ) {
11466 strcat( buf, " " );
11469 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11470 sprintf( buf+strlen(buf), "%s%.2f",
11471 pvInfoList[idx].score >= 0 ? "+" : "",
11472 pvInfoList[idx].score / 100.0 );
11478 /* Save game in PGN style and close the file */
11483 int i, offset, linelen, newblock;
11487 int movelen, numlen, blank;
11488 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11490 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11492 tm = time((time_t *) NULL);
11494 PrintPGNTags(f, &gameInfo);
11496 if (backwardMostMove > 0 || startedFromSetupPosition) {
11497 char *fen = PositionToFEN(backwardMostMove, NULL);
11498 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11499 fprintf(f, "\n{--------------\n");
11500 PrintPosition(f, backwardMostMove);
11501 fprintf(f, "--------------}\n");
11505 /* [AS] Out of book annotation */
11506 if( appData.saveOutOfBookInfo ) {
11509 GetOutOfBookInfo( buf );
11511 if( buf[0] != '\0' ) {
11512 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11519 i = backwardMostMove;
11523 while (i < forwardMostMove) {
11524 /* Print comments preceding this move */
11525 if (commentList[i] != NULL) {
11526 if (linelen > 0) fprintf(f, "\n");
11527 fprintf(f, "%s", commentList[i]);
11532 /* Format move number */
11534 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11537 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11539 numtext[0] = NULLCHAR;
11541 numlen = strlen(numtext);
11544 /* Print move number */
11545 blank = linelen > 0 && numlen > 0;
11546 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11555 fprintf(f, "%s", numtext);
11559 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11560 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11563 blank = linelen > 0 && movelen > 0;
11564 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11573 fprintf(f, "%s", move_buffer);
11574 linelen += movelen;
11576 /* [AS] Add PV info if present */
11577 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11578 /* [HGM] add time */
11579 char buf[MSG_SIZ]; int seconds;
11581 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11587 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11590 seconds = (seconds + 4)/10; // round to full seconds
11592 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11594 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11597 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11598 pvInfoList[i].score >= 0 ? "+" : "",
11599 pvInfoList[i].score / 100.0,
11600 pvInfoList[i].depth,
11603 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11605 /* Print score/depth */
11606 blank = linelen > 0 && movelen > 0;
11607 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11616 fprintf(f, "%s", move_buffer);
11617 linelen += movelen;
11623 /* Start a new line */
11624 if (linelen > 0) fprintf(f, "\n");
11626 /* Print comments after last move */
11627 if (commentList[i] != NULL) {
11628 fprintf(f, "%s\n", commentList[i]);
11632 if (gameInfo.resultDetails != NULL &&
11633 gameInfo.resultDetails[0] != NULLCHAR) {
11634 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11635 PGNResult(gameInfo.result));
11637 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11641 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11645 /* Save game in old style and close the file */
11647 SaveGameOldStyle(f)
11653 tm = time((time_t *) NULL);
11655 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11658 if (backwardMostMove > 0 || startedFromSetupPosition) {
11659 fprintf(f, "\n[--------------\n");
11660 PrintPosition(f, backwardMostMove);
11661 fprintf(f, "--------------]\n");
11666 i = backwardMostMove;
11667 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11669 while (i < forwardMostMove) {
11670 if (commentList[i] != NULL) {
11671 fprintf(f, "[%s]\n", commentList[i]);
11674 if ((i % 2) == 1) {
11675 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11678 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11680 if (commentList[i] != NULL) {
11684 if (i >= forwardMostMove) {
11688 fprintf(f, "%s\n", parseList[i]);
11693 if (commentList[i] != NULL) {
11694 fprintf(f, "[%s]\n", commentList[i]);
11697 /* This isn't really the old style, but it's close enough */
11698 if (gameInfo.resultDetails != NULL &&
11699 gameInfo.resultDetails[0] != NULLCHAR) {
11700 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11701 gameInfo.resultDetails);
11703 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11710 /* Save the current game to open file f and close the file */
11712 SaveGame(f, dummy, dummy2)
11717 if (gameMode == EditPosition) EditPositionDone(TRUE);
11718 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11719 if (appData.oldSaveStyle)
11720 return SaveGameOldStyle(f);
11722 return SaveGamePGN(f);
11725 /* Save the current position to the given file */
11727 SavePositionToFile(filename)
11733 if (strcmp(filename, "-") == 0) {
11734 return SavePosition(stdout, 0, NULL);
11736 f = fopen(filename, "a");
11738 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11739 DisplayError(buf, errno);
11742 safeStrCpy(buf, lastMsg, MSG_SIZ);
11743 DisplayMessage(_("Waiting for access to save file"), "");
11744 flock(fileno(f), LOCK_EX); // [HGM] lock
11745 DisplayMessage(_("Saving position"), "");
11746 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11747 SavePosition(f, 0, NULL);
11748 DisplayMessage(buf, "");
11754 /* Save the current position to the given open file and close the file */
11756 SavePosition(f, dummy, dummy2)
11764 if (gameMode == EditPosition) EditPositionDone(TRUE);
11765 if (appData.oldSaveStyle) {
11766 tm = time((time_t *) NULL);
11768 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11770 fprintf(f, "[--------------\n");
11771 PrintPosition(f, currentMove);
11772 fprintf(f, "--------------]\n");
11774 fen = PositionToFEN(currentMove, NULL);
11775 fprintf(f, "%s\n", fen);
11783 ReloadCmailMsgEvent(unregister)
11787 static char *inFilename = NULL;
11788 static char *outFilename;
11790 struct stat inbuf, outbuf;
11793 /* Any registered moves are unregistered if unregister is set, */
11794 /* i.e. invoked by the signal handler */
11796 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11797 cmailMoveRegistered[i] = FALSE;
11798 if (cmailCommentList[i] != NULL) {
11799 free(cmailCommentList[i]);
11800 cmailCommentList[i] = NULL;
11803 nCmailMovesRegistered = 0;
11806 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11807 cmailResult[i] = CMAIL_NOT_RESULT;
11811 if (inFilename == NULL) {
11812 /* Because the filenames are static they only get malloced once */
11813 /* and they never get freed */
11814 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11815 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11817 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11818 sprintf(outFilename, "%s.out", appData.cmailGameName);
11821 status = stat(outFilename, &outbuf);
11823 cmailMailedMove = FALSE;
11825 status = stat(inFilename, &inbuf);
11826 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11829 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11830 counts the games, notes how each one terminated, etc.
11832 It would be nice to remove this kludge and instead gather all
11833 the information while building the game list. (And to keep it
11834 in the game list nodes instead of having a bunch of fixed-size
11835 parallel arrays.) Note this will require getting each game's
11836 termination from the PGN tags, as the game list builder does
11837 not process the game moves. --mann
11839 cmailMsgLoaded = TRUE;
11840 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11842 /* Load first game in the file or popup game menu */
11843 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11845 #endif /* !WIN32 */
11853 char string[MSG_SIZ];
11855 if ( cmailMailedMove
11856 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11857 return TRUE; /* Allow free viewing */
11860 /* Unregister move to ensure that we don't leave RegisterMove */
11861 /* with the move registered when the conditions for registering no */
11863 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11864 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11865 nCmailMovesRegistered --;
11867 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11869 free(cmailCommentList[lastLoadGameNumber - 1]);
11870 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11874 if (cmailOldMove == -1) {
11875 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11879 if (currentMove > cmailOldMove + 1) {
11880 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11884 if (currentMove < cmailOldMove) {
11885 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11889 if (forwardMostMove > currentMove) {
11890 /* Silently truncate extra moves */
11894 if ( (currentMove == cmailOldMove + 1)
11895 || ( (currentMove == cmailOldMove)
11896 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11897 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11898 if (gameInfo.result != GameUnfinished) {
11899 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11902 if (commentList[currentMove] != NULL) {
11903 cmailCommentList[lastLoadGameNumber - 1]
11904 = StrSave(commentList[currentMove]);
11906 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11908 if (appData.debugMode)
11909 fprintf(debugFP, "Saving %s for game %d\n",
11910 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11912 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11914 f = fopen(string, "w");
11915 if (appData.oldSaveStyle) {
11916 SaveGameOldStyle(f); /* also closes the file */
11918 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11919 f = fopen(string, "w");
11920 SavePosition(f, 0, NULL); /* also closes the file */
11922 fprintf(f, "{--------------\n");
11923 PrintPosition(f, currentMove);
11924 fprintf(f, "--------------}\n\n");
11926 SaveGame(f, 0, NULL); /* also closes the file*/
11929 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11930 nCmailMovesRegistered ++;
11931 } else if (nCmailGames == 1) {
11932 DisplayError(_("You have not made a move yet"), 0);
11943 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11944 FILE *commandOutput;
11945 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11946 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11952 if (! cmailMsgLoaded) {
11953 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11957 if (nCmailGames == nCmailResults) {
11958 DisplayError(_("No unfinished games"), 0);
11962 #if CMAIL_PROHIBIT_REMAIL
11963 if (cmailMailedMove) {
11964 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);
11965 DisplayError(msg, 0);
11970 if (! (cmailMailedMove || RegisterMove())) return;
11972 if ( cmailMailedMove
11973 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11974 snprintf(string, MSG_SIZ, partCommandString,
11975 appData.debugMode ? " -v" : "", appData.cmailGameName);
11976 commandOutput = popen(string, "r");
11978 if (commandOutput == NULL) {
11979 DisplayError(_("Failed to invoke cmail"), 0);
11981 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11982 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11984 if (nBuffers > 1) {
11985 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11986 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11987 nBytes = MSG_SIZ - 1;
11989 (void) memcpy(msg, buffer, nBytes);
11991 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11993 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11994 cmailMailedMove = TRUE; /* Prevent >1 moves */
11997 for (i = 0; i < nCmailGames; i ++) {
11998 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12003 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12005 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12007 appData.cmailGameName,
12009 LoadGameFromFile(buffer, 1, buffer, FALSE);
12010 cmailMsgLoaded = FALSE;
12014 DisplayInformation(msg);
12015 pclose(commandOutput);
12018 if ((*cmailMsg) != '\0') {
12019 DisplayInformation(cmailMsg);
12024 #endif /* !WIN32 */
12033 int prependComma = 0;
12035 char string[MSG_SIZ]; /* Space for game-list */
12038 if (!cmailMsgLoaded) return "";
12040 if (cmailMailedMove) {
12041 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12043 /* Create a list of games left */
12044 snprintf(string, MSG_SIZ, "[");
12045 for (i = 0; i < nCmailGames; i ++) {
12046 if (! ( cmailMoveRegistered[i]
12047 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12048 if (prependComma) {
12049 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12051 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12055 strcat(string, number);
12058 strcat(string, "]");
12060 if (nCmailMovesRegistered + nCmailResults == 0) {
12061 switch (nCmailGames) {
12063 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12067 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12071 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12076 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12078 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12083 if (nCmailResults == nCmailGames) {
12084 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12086 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12091 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12103 if (gameMode == Training)
12104 SetTrainingModeOff();
12107 cmailMsgLoaded = FALSE;
12108 if (appData.icsActive) {
12109 SendToICS(ics_prefix);
12110 SendToICS("refresh\n");
12120 /* Give up on clean exit */
12124 /* Keep trying for clean exit */
12128 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12130 if (telnetISR != NULL) {
12131 RemoveInputSource(telnetISR);
12133 if (icsPR != NoProc) {
12134 DestroyChildProcess(icsPR, TRUE);
12137 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12138 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12140 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12141 /* make sure this other one finishes before killing it! */
12142 if(endingGame) { int count = 0;
12143 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12144 while(endingGame && count++ < 10) DoSleep(1);
12145 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12148 /* Kill off chess programs */
12149 if (first.pr != NoProc) {
12152 DoSleep( appData.delayBeforeQuit );
12153 SendToProgram("quit\n", &first);
12154 DoSleep( appData.delayAfterQuit );
12155 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12157 if (second.pr != NoProc) {
12158 DoSleep( appData.delayBeforeQuit );
12159 SendToProgram("quit\n", &second);
12160 DoSleep( appData.delayAfterQuit );
12161 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12163 if (first.isr != NULL) {
12164 RemoveInputSource(first.isr);
12166 if (second.isr != NULL) {
12167 RemoveInputSource(second.isr);
12170 ShutDownFrontEnd();
12177 if (appData.debugMode)
12178 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12182 if (gameMode == MachinePlaysWhite ||
12183 gameMode == MachinePlaysBlack) {
12186 DisplayBothClocks();
12188 if (gameMode == PlayFromGameFile) {
12189 if (appData.timeDelay >= 0)
12190 AutoPlayGameLoop();
12191 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12192 Reset(FALSE, TRUE);
12193 SendToICS(ics_prefix);
12194 SendToICS("refresh\n");
12195 } else if (currentMove < forwardMostMove) {
12196 ForwardInner(forwardMostMove);
12198 pauseExamInvalid = FALSE;
12200 switch (gameMode) {
12204 pauseExamForwardMostMove = forwardMostMove;
12205 pauseExamInvalid = FALSE;
12208 case IcsPlayingWhite:
12209 case IcsPlayingBlack:
12213 case PlayFromGameFile:
12214 (void) StopLoadGameTimer();
12218 case BeginningOfGame:
12219 if (appData.icsActive) return;
12220 /* else fall through */
12221 case MachinePlaysWhite:
12222 case MachinePlaysBlack:
12223 case TwoMachinesPlay:
12224 if (forwardMostMove == 0)
12225 return; /* don't pause if no one has moved */
12226 if ((gameMode == MachinePlaysWhite &&
12227 !WhiteOnMove(forwardMostMove)) ||
12228 (gameMode == MachinePlaysBlack &&
12229 WhiteOnMove(forwardMostMove))) {
12242 char title[MSG_SIZ];
12244 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12245 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12247 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12248 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12249 parseList[currentMove - 1]);
12252 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12259 char *tags = PGNTags(&gameInfo);
12260 EditTagsPopUp(tags, NULL);
12267 if (appData.noChessProgram || gameMode == AnalyzeMode)
12270 if (gameMode != AnalyzeFile) {
12271 if (!appData.icsEngineAnalyze) {
12273 if (gameMode != EditGame) return;
12275 ResurrectChessProgram();
12276 SendToProgram("analyze\n", &first);
12277 first.analyzing = TRUE;
12278 /*first.maybeThinking = TRUE;*/
12279 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12280 EngineOutputPopUp();
12282 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12287 StartAnalysisClock();
12288 GetTimeMark(&lastNodeCountTime);
12295 if (appData.noChessProgram || gameMode == AnalyzeFile)
12298 if (gameMode != AnalyzeMode) {
12300 if (gameMode != EditGame) return;
12301 ResurrectChessProgram();
12302 SendToProgram("analyze\n", &first);
12303 first.analyzing = TRUE;
12304 /*first.maybeThinking = TRUE;*/
12305 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12306 EngineOutputPopUp();
12308 gameMode = AnalyzeFile;
12313 StartAnalysisClock();
12314 GetTimeMark(&lastNodeCountTime);
12319 MachineWhiteEvent()
12322 char *bookHit = NULL;
12324 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12328 if (gameMode == PlayFromGameFile ||
12329 gameMode == TwoMachinesPlay ||
12330 gameMode == Training ||
12331 gameMode == AnalyzeMode ||
12332 gameMode == EndOfGame)
12335 if (gameMode == EditPosition)
12336 EditPositionDone(TRUE);
12338 if (!WhiteOnMove(currentMove)) {
12339 DisplayError(_("It is not White's turn"), 0);
12343 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12346 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12347 gameMode == AnalyzeFile)
12350 ResurrectChessProgram(); /* in case it isn't running */
12351 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12352 gameMode = MachinePlaysWhite;
12355 gameMode = MachinePlaysWhite;
12359 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12361 if (first.sendName) {
12362 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12363 SendToProgram(buf, &first);
12365 if (first.sendTime) {
12366 if (first.useColors) {
12367 SendToProgram("black\n", &first); /*gnu kludge*/
12369 SendTimeRemaining(&first, TRUE);
12371 if (first.useColors) {
12372 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12374 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12375 SetMachineThinkingEnables();
12376 first.maybeThinking = TRUE;
12380 if (appData.autoFlipView && !flipView) {
12381 flipView = !flipView;
12382 DrawPosition(FALSE, NULL);
12383 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12386 if(bookHit) { // [HGM] book: simulate book reply
12387 static char bookMove[MSG_SIZ]; // a bit generous?
12389 programStats.nodes = programStats.depth = programStats.time =
12390 programStats.score = programStats.got_only_move = 0;
12391 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12393 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12394 strcat(bookMove, bookHit);
12395 HandleMachineMove(bookMove, &first);
12400 MachineBlackEvent()
12403 char *bookHit = NULL;
12405 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12409 if (gameMode == PlayFromGameFile ||
12410 gameMode == TwoMachinesPlay ||
12411 gameMode == Training ||
12412 gameMode == AnalyzeMode ||
12413 gameMode == EndOfGame)
12416 if (gameMode == EditPosition)
12417 EditPositionDone(TRUE);
12419 if (WhiteOnMove(currentMove)) {
12420 DisplayError(_("It is not Black's turn"), 0);
12424 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12427 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12428 gameMode == AnalyzeFile)
12431 ResurrectChessProgram(); /* in case it isn't running */
12432 gameMode = MachinePlaysBlack;
12436 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12438 if (first.sendName) {
12439 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12440 SendToProgram(buf, &first);
12442 if (first.sendTime) {
12443 if (first.useColors) {
12444 SendToProgram("white\n", &first); /*gnu kludge*/
12446 SendTimeRemaining(&first, FALSE);
12448 if (first.useColors) {
12449 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12451 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12452 SetMachineThinkingEnables();
12453 first.maybeThinking = TRUE;
12456 if (appData.autoFlipView && flipView) {
12457 flipView = !flipView;
12458 DrawPosition(FALSE, NULL);
12459 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12461 if(bookHit) { // [HGM] book: simulate book reply
12462 static char bookMove[MSG_SIZ]; // a bit generous?
12464 programStats.nodes = programStats.depth = programStats.time =
12465 programStats.score = programStats.got_only_move = 0;
12466 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12468 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12469 strcat(bookMove, bookHit);
12470 HandleMachineMove(bookMove, &first);
12476 DisplayTwoMachinesTitle()
12479 if (appData.matchGames > 0) {
12480 if (first.twoMachinesColor[0] == 'w') {
12481 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12482 gameInfo.white, gameInfo.black,
12483 first.matchWins, second.matchWins,
12484 matchGame - 1 - (first.matchWins + second.matchWins));
12486 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12487 gameInfo.white, gameInfo.black,
12488 second.matchWins, first.matchWins,
12489 matchGame - 1 - (first.matchWins + second.matchWins));
12492 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12498 SettingsMenuIfReady()
12500 if (second.lastPing != second.lastPong) {
12501 DisplayMessage("", _("Waiting for second chess program"));
12502 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12506 DisplayMessage("", "");
12507 SettingsPopUp(&second);
12511 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12514 if (cps->pr == NULL) {
12515 StartChessProgram(cps);
12516 if (cps->protocolVersion == 1) {
12519 /* kludge: allow timeout for initial "feature" command */
12521 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12522 DisplayMessage("", buf);
12523 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12531 TwoMachinesEvent P((void))
12535 ChessProgramState *onmove;
12536 char *bookHit = NULL;
12537 static int stalling = 0;
12541 if (appData.noChessProgram) return;
12543 switch (gameMode) {
12544 case TwoMachinesPlay:
12546 case MachinePlaysWhite:
12547 case MachinePlaysBlack:
12548 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12549 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12553 case BeginningOfGame:
12554 case PlayFromGameFile:
12557 if (gameMode != EditGame) return;
12560 EditPositionDone(TRUE);
12571 // forwardMostMove = currentMove;
12572 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12574 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12576 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12577 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12578 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12582 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12583 SendToProgram("force\n", &second);
12585 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12588 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12589 if(appData.matchPause>10000 || appData.matchPause<10)
12590 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12591 wait = SubtractTimeMarks(&now, &pauseStart);
12592 if(wait < appData.matchPause) {
12593 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12597 DisplayMessage("", "");
12598 if (startedFromSetupPosition) {
12599 SendBoard(&second, backwardMostMove);
12600 if (appData.debugMode) {
12601 fprintf(debugFP, "Two Machines\n");
12604 for (i = backwardMostMove; i < forwardMostMove; i++) {
12605 SendMoveToProgram(i, &second);
12608 gameMode = TwoMachinesPlay;
12612 DisplayTwoMachinesTitle();
12614 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12619 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12620 SendToProgram(first.computerString, &first);
12621 if (first.sendName) {
12622 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12623 SendToProgram(buf, &first);
12625 SendToProgram(second.computerString, &second);
12626 if (second.sendName) {
12627 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12628 SendToProgram(buf, &second);
12632 if (!first.sendTime || !second.sendTime) {
12633 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12634 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12636 if (onmove->sendTime) {
12637 if (onmove->useColors) {
12638 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12640 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12642 if (onmove->useColors) {
12643 SendToProgram(onmove->twoMachinesColor, onmove);
12645 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12646 // SendToProgram("go\n", onmove);
12647 onmove->maybeThinking = TRUE;
12648 SetMachineThinkingEnables();
12652 if(bookHit) { // [HGM] book: simulate book reply
12653 static char bookMove[MSG_SIZ]; // a bit generous?
12655 programStats.nodes = programStats.depth = programStats.time =
12656 programStats.score = programStats.got_only_move = 0;
12657 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12659 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12660 strcat(bookMove, bookHit);
12661 savedMessage = bookMove; // args for deferred call
12662 savedState = onmove;
12663 ScheduleDelayedEvent(DeferredBookMove, 1);
12670 if (gameMode == Training) {
12671 SetTrainingModeOff();
12672 gameMode = PlayFromGameFile;
12673 DisplayMessage("", _("Training mode off"));
12675 gameMode = Training;
12676 animateTraining = appData.animate;
12678 /* make sure we are not already at the end of the game */
12679 if (currentMove < forwardMostMove) {
12680 SetTrainingModeOn();
12681 DisplayMessage("", _("Training mode on"));
12683 gameMode = PlayFromGameFile;
12684 DisplayError(_("Already at end of game"), 0);
12693 if (!appData.icsActive) return;
12694 switch (gameMode) {
12695 case IcsPlayingWhite:
12696 case IcsPlayingBlack:
12699 case BeginningOfGame:
12707 EditPositionDone(TRUE);
12720 gameMode = IcsIdle;
12731 switch (gameMode) {
12733 SetTrainingModeOff();
12735 case MachinePlaysWhite:
12736 case MachinePlaysBlack:
12737 case BeginningOfGame:
12738 SendToProgram("force\n", &first);
12739 SetUserThinkingEnables();
12741 case PlayFromGameFile:
12742 (void) StopLoadGameTimer();
12743 if (gameFileFP != NULL) {
12748 EditPositionDone(TRUE);
12753 SendToProgram("force\n", &first);
12755 case TwoMachinesPlay:
12756 GameEnds(EndOfFile, NULL, GE_PLAYER);
12757 ResurrectChessProgram();
12758 SetUserThinkingEnables();
12761 ResurrectChessProgram();
12763 case IcsPlayingBlack:
12764 case IcsPlayingWhite:
12765 DisplayError(_("Warning: You are still playing a game"), 0);
12768 DisplayError(_("Warning: You are still observing a game"), 0);
12771 DisplayError(_("Warning: You are still examining a game"), 0);
12782 first.offeredDraw = second.offeredDraw = 0;
12784 if (gameMode == PlayFromGameFile) {
12785 whiteTimeRemaining = timeRemaining[0][currentMove];
12786 blackTimeRemaining = timeRemaining[1][currentMove];
12790 if (gameMode == MachinePlaysWhite ||
12791 gameMode == MachinePlaysBlack ||
12792 gameMode == TwoMachinesPlay ||
12793 gameMode == EndOfGame) {
12794 i = forwardMostMove;
12795 while (i > currentMove) {
12796 SendToProgram("undo\n", &first);
12799 whiteTimeRemaining = timeRemaining[0][currentMove];
12800 blackTimeRemaining = timeRemaining[1][currentMove];
12801 DisplayBothClocks();
12802 if (whiteFlag || blackFlag) {
12803 whiteFlag = blackFlag = 0;
12808 gameMode = EditGame;
12815 EditPositionEvent()
12817 if (gameMode == EditPosition) {
12823 if (gameMode != EditGame) return;
12825 gameMode = EditPosition;
12828 if (currentMove > 0)
12829 CopyBoard(boards[0], boards[currentMove]);
12831 blackPlaysFirst = !WhiteOnMove(currentMove);
12833 currentMove = forwardMostMove = backwardMostMove = 0;
12834 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12841 /* [DM] icsEngineAnalyze - possible call from other functions */
12842 if (appData.icsEngineAnalyze) {
12843 appData.icsEngineAnalyze = FALSE;
12845 DisplayMessage("",_("Close ICS engine analyze..."));
12847 if (first.analysisSupport && first.analyzing) {
12848 SendToProgram("exit\n", &first);
12849 first.analyzing = FALSE;
12851 thinkOutput[0] = NULLCHAR;
12855 EditPositionDone(Boolean fakeRights)
12857 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12859 startedFromSetupPosition = TRUE;
12860 InitChessProgram(&first, FALSE);
12861 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12862 boards[0][EP_STATUS] = EP_NONE;
12863 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12864 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12865 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12866 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12867 } else boards[0][CASTLING][2] = NoRights;
12868 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12869 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12870 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12871 } else boards[0][CASTLING][5] = NoRights;
12873 SendToProgram("force\n", &first);
12874 if (blackPlaysFirst) {
12875 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12876 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12877 currentMove = forwardMostMove = backwardMostMove = 1;
12878 CopyBoard(boards[1], boards[0]);
12880 currentMove = forwardMostMove = backwardMostMove = 0;
12882 SendBoard(&first, forwardMostMove);
12883 if (appData.debugMode) {
12884 fprintf(debugFP, "EditPosDone\n");
12887 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12888 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12889 gameMode = EditGame;
12891 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12892 ClearHighlights(); /* [AS] */
12895 /* Pause for `ms' milliseconds */
12896 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12906 } while (SubtractTimeMarks(&m2, &m1) < ms);
12909 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12911 SendMultiLineToICS(buf)
12914 char temp[MSG_SIZ+1], *p;
12921 strncpy(temp, buf, len);
12926 if (*p == '\n' || *p == '\r')
12931 strcat(temp, "\n");
12933 SendToPlayer(temp, strlen(temp));
12937 SetWhiteToPlayEvent()
12939 if (gameMode == EditPosition) {
12940 blackPlaysFirst = FALSE;
12941 DisplayBothClocks(); /* works because currentMove is 0 */
12942 } else if (gameMode == IcsExamining) {
12943 SendToICS(ics_prefix);
12944 SendToICS("tomove white\n");
12949 SetBlackToPlayEvent()
12951 if (gameMode == EditPosition) {
12952 blackPlaysFirst = TRUE;
12953 currentMove = 1; /* kludge */
12954 DisplayBothClocks();
12956 } else if (gameMode == IcsExamining) {
12957 SendToICS(ics_prefix);
12958 SendToICS("tomove black\n");
12963 EditPositionMenuEvent(selection, x, y)
12964 ChessSquare selection;
12968 ChessSquare piece = boards[0][y][x];
12970 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12972 switch (selection) {
12974 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12975 SendToICS(ics_prefix);
12976 SendToICS("bsetup clear\n");
12977 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12978 SendToICS(ics_prefix);
12979 SendToICS("clearboard\n");
12981 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12982 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12983 for (y = 0; y < BOARD_HEIGHT; y++) {
12984 if (gameMode == IcsExamining) {
12985 if (boards[currentMove][y][x] != EmptySquare) {
12986 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12991 boards[0][y][x] = p;
12996 if (gameMode == EditPosition) {
12997 DrawPosition(FALSE, boards[0]);
13002 SetWhiteToPlayEvent();
13006 SetBlackToPlayEvent();
13010 if (gameMode == IcsExamining) {
13011 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13012 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13015 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13016 if(x == BOARD_LEFT-2) {
13017 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13018 boards[0][y][1] = 0;
13020 if(x == BOARD_RGHT+1) {
13021 if(y >= gameInfo.holdingsSize) break;
13022 boards[0][y][BOARD_WIDTH-2] = 0;
13025 boards[0][y][x] = EmptySquare;
13026 DrawPosition(FALSE, boards[0]);
13031 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13032 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13033 selection = (ChessSquare) (PROMOTED piece);
13034 } else if(piece == EmptySquare) selection = WhiteSilver;
13035 else selection = (ChessSquare)((int)piece - 1);
13039 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13040 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13041 selection = (ChessSquare) (DEMOTED piece);
13042 } else if(piece == EmptySquare) selection = BlackSilver;
13043 else selection = (ChessSquare)((int)piece + 1);
13048 if(gameInfo.variant == VariantShatranj ||
13049 gameInfo.variant == VariantXiangqi ||
13050 gameInfo.variant == VariantCourier ||
13051 gameInfo.variant == VariantMakruk )
13052 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13057 if(gameInfo.variant == VariantXiangqi)
13058 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13059 if(gameInfo.variant == VariantKnightmate)
13060 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13063 if (gameMode == IcsExamining) {
13064 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13065 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13066 PieceToChar(selection), AAA + x, ONE + y);
13069 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13071 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13072 n = PieceToNumber(selection - BlackPawn);
13073 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13074 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13075 boards[0][BOARD_HEIGHT-1-n][1]++;
13077 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13078 n = PieceToNumber(selection);
13079 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13080 boards[0][n][BOARD_WIDTH-1] = selection;
13081 boards[0][n][BOARD_WIDTH-2]++;
13084 boards[0][y][x] = selection;
13085 DrawPosition(TRUE, boards[0]);
13093 DropMenuEvent(selection, x, y)
13094 ChessSquare selection;
13097 ChessMove moveType;
13099 switch (gameMode) {
13100 case IcsPlayingWhite:
13101 case MachinePlaysBlack:
13102 if (!WhiteOnMove(currentMove)) {
13103 DisplayMoveError(_("It is Black's turn"));
13106 moveType = WhiteDrop;
13108 case IcsPlayingBlack:
13109 case MachinePlaysWhite:
13110 if (WhiteOnMove(currentMove)) {
13111 DisplayMoveError(_("It is White's turn"));
13114 moveType = BlackDrop;
13117 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13123 if (moveType == BlackDrop && selection < BlackPawn) {
13124 selection = (ChessSquare) ((int) selection
13125 + (int) BlackPawn - (int) WhitePawn);
13127 if (boards[currentMove][y][x] != EmptySquare) {
13128 DisplayMoveError(_("That square is occupied"));
13132 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13138 /* Accept a pending offer of any kind from opponent */
13140 if (appData.icsActive) {
13141 SendToICS(ics_prefix);
13142 SendToICS("accept\n");
13143 } else if (cmailMsgLoaded) {
13144 if (currentMove == cmailOldMove &&
13145 commentList[cmailOldMove] != NULL &&
13146 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13147 "Black offers a draw" : "White offers a draw")) {
13149 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13150 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13152 DisplayError(_("There is no pending offer on this move"), 0);
13153 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13156 /* Not used for offers from chess program */
13163 /* Decline a pending offer of any kind from opponent */
13165 if (appData.icsActive) {
13166 SendToICS(ics_prefix);
13167 SendToICS("decline\n");
13168 } else if (cmailMsgLoaded) {
13169 if (currentMove == cmailOldMove &&
13170 commentList[cmailOldMove] != NULL &&
13171 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13172 "Black offers a draw" : "White offers a draw")) {
13174 AppendComment(cmailOldMove, "Draw declined", TRUE);
13175 DisplayComment(cmailOldMove - 1, "Draw declined");
13178 DisplayError(_("There is no pending offer on this move"), 0);
13181 /* Not used for offers from chess program */
13188 /* Issue ICS rematch command */
13189 if (appData.icsActive) {
13190 SendToICS(ics_prefix);
13191 SendToICS("rematch\n");
13198 /* Call your opponent's flag (claim a win on time) */
13199 if (appData.icsActive) {
13200 SendToICS(ics_prefix);
13201 SendToICS("flag\n");
13203 switch (gameMode) {
13206 case MachinePlaysWhite:
13209 GameEnds(GameIsDrawn, "Both players ran out of time",
13212 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13214 DisplayError(_("Your opponent is not out of time"), 0);
13217 case MachinePlaysBlack:
13220 GameEnds(GameIsDrawn, "Both players ran out of time",
13223 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13225 DisplayError(_("Your opponent is not out of time"), 0);
13233 ClockClick(int which)
13234 { // [HGM] code moved to back-end from winboard.c
13235 if(which) { // black clock
13236 if (gameMode == EditPosition || gameMode == IcsExamining) {
13237 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13238 SetBlackToPlayEvent();
13239 } else if (gameMode == EditGame || shiftKey) {
13240 AdjustClock(which, -1);
13241 } else if (gameMode == IcsPlayingWhite ||
13242 gameMode == MachinePlaysBlack) {
13245 } else { // white clock
13246 if (gameMode == EditPosition || gameMode == IcsExamining) {
13247 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13248 SetWhiteToPlayEvent();
13249 } else if (gameMode == EditGame || shiftKey) {
13250 AdjustClock(which, -1);
13251 } else if (gameMode == IcsPlayingBlack ||
13252 gameMode == MachinePlaysWhite) {
13261 /* Offer draw or accept pending draw offer from opponent */
13263 if (appData.icsActive) {
13264 /* Note: tournament rules require draw offers to be
13265 made after you make your move but before you punch
13266 your clock. Currently ICS doesn't let you do that;
13267 instead, you immediately punch your clock after making
13268 a move, but you can offer a draw at any time. */
13270 SendToICS(ics_prefix);
13271 SendToICS("draw\n");
13272 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13273 } else if (cmailMsgLoaded) {
13274 if (currentMove == cmailOldMove &&
13275 commentList[cmailOldMove] != NULL &&
13276 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13277 "Black offers a draw" : "White offers a draw")) {
13278 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13279 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13280 } else if (currentMove == cmailOldMove + 1) {
13281 char *offer = WhiteOnMove(cmailOldMove) ?
13282 "White offers a draw" : "Black offers a draw";
13283 AppendComment(currentMove, offer, TRUE);
13284 DisplayComment(currentMove - 1, offer);
13285 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13287 DisplayError(_("You must make your move before offering a draw"), 0);
13288 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13290 } else if (first.offeredDraw) {
13291 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13293 if (first.sendDrawOffers) {
13294 SendToProgram("draw\n", &first);
13295 userOfferedDraw = TRUE;
13303 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13305 if (appData.icsActive) {
13306 SendToICS(ics_prefix);
13307 SendToICS("adjourn\n");
13309 /* Currently GNU Chess doesn't offer or accept Adjourns */
13317 /* Offer Abort or accept pending Abort offer from opponent */
13319 if (appData.icsActive) {
13320 SendToICS(ics_prefix);
13321 SendToICS("abort\n");
13323 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13330 /* Resign. You can do this even if it's not your turn. */
13332 if (appData.icsActive) {
13333 SendToICS(ics_prefix);
13334 SendToICS("resign\n");
13336 switch (gameMode) {
13337 case MachinePlaysWhite:
13338 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13340 case MachinePlaysBlack:
13341 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13344 if (cmailMsgLoaded) {
13346 if (WhiteOnMove(cmailOldMove)) {
13347 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13349 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13351 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13362 StopObservingEvent()
13364 /* Stop observing current games */
13365 SendToICS(ics_prefix);
13366 SendToICS("unobserve\n");
13370 StopExaminingEvent()
13372 /* Stop observing current game */
13373 SendToICS(ics_prefix);
13374 SendToICS("unexamine\n");
13378 ForwardInner(target)
13383 if (appData.debugMode)
13384 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13385 target, currentMove, forwardMostMove);
13387 if (gameMode == EditPosition)
13390 if (gameMode == PlayFromGameFile && !pausing)
13393 if (gameMode == IcsExamining && pausing)
13394 limit = pauseExamForwardMostMove;
13396 limit = forwardMostMove;
13398 if (target > limit) target = limit;
13400 if (target > 0 && moveList[target - 1][0]) {
13401 int fromX, fromY, toX, toY;
13402 toX = moveList[target - 1][2] - AAA;
13403 toY = moveList[target - 1][3] - ONE;
13404 if (moveList[target - 1][1] == '@') {
13405 if (appData.highlightLastMove) {
13406 SetHighlights(-1, -1, toX, toY);
13409 fromX = moveList[target - 1][0] - AAA;
13410 fromY = moveList[target - 1][1] - ONE;
13411 if (target == currentMove + 1) {
13412 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13414 if (appData.highlightLastMove) {
13415 SetHighlights(fromX, fromY, toX, toY);
13419 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13420 gameMode == Training || gameMode == PlayFromGameFile ||
13421 gameMode == AnalyzeFile) {
13422 while (currentMove < target) {
13423 SendMoveToProgram(currentMove++, &first);
13426 currentMove = target;
13429 if (gameMode == EditGame || gameMode == EndOfGame) {
13430 whiteTimeRemaining = timeRemaining[0][currentMove];
13431 blackTimeRemaining = timeRemaining[1][currentMove];
13433 DisplayBothClocks();
13434 DisplayMove(currentMove - 1);
13435 DrawPosition(FALSE, boards[currentMove]);
13436 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13437 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13438 DisplayComment(currentMove - 1, commentList[currentMove]);
13446 if (gameMode == IcsExamining && !pausing) {
13447 SendToICS(ics_prefix);
13448 SendToICS("forward\n");
13450 ForwardInner(currentMove + 1);
13457 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13458 /* to optimze, we temporarily turn off analysis mode while we feed
13459 * the remaining moves to the engine. Otherwise we get analysis output
13462 if (first.analysisSupport) {
13463 SendToProgram("exit\nforce\n", &first);
13464 first.analyzing = FALSE;
13468 if (gameMode == IcsExamining && !pausing) {
13469 SendToICS(ics_prefix);
13470 SendToICS("forward 999999\n");
13472 ForwardInner(forwardMostMove);
13475 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13476 /* we have fed all the moves, so reactivate analysis mode */
13477 SendToProgram("analyze\n", &first);
13478 first.analyzing = TRUE;
13479 /*first.maybeThinking = TRUE;*/
13480 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13485 BackwardInner(target)
13488 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13490 if (appData.debugMode)
13491 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13492 target, currentMove, forwardMostMove);
13494 if (gameMode == EditPosition) return;
13495 if (currentMove <= backwardMostMove) {
13497 DrawPosition(full_redraw, boards[currentMove]);
13500 if (gameMode == PlayFromGameFile && !pausing)
13503 if (moveList[target][0]) {
13504 int fromX, fromY, toX, toY;
13505 toX = moveList[target][2] - AAA;
13506 toY = moveList[target][3] - ONE;
13507 if (moveList[target][1] == '@') {
13508 if (appData.highlightLastMove) {
13509 SetHighlights(-1, -1, toX, toY);
13512 fromX = moveList[target][0] - AAA;
13513 fromY = moveList[target][1] - ONE;
13514 if (target == currentMove - 1) {
13515 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13517 if (appData.highlightLastMove) {
13518 SetHighlights(fromX, fromY, toX, toY);
13522 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13523 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13524 while (currentMove > target) {
13525 SendToProgram("undo\n", &first);
13529 currentMove = target;
13532 if (gameMode == EditGame || gameMode == EndOfGame) {
13533 whiteTimeRemaining = timeRemaining[0][currentMove];
13534 blackTimeRemaining = timeRemaining[1][currentMove];
13536 DisplayBothClocks();
13537 DisplayMove(currentMove - 1);
13538 DrawPosition(full_redraw, boards[currentMove]);
13539 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13540 // [HGM] PV info: routine tests if comment empty
13541 DisplayComment(currentMove - 1, commentList[currentMove]);
13547 if (gameMode == IcsExamining && !pausing) {
13548 SendToICS(ics_prefix);
13549 SendToICS("backward\n");
13551 BackwardInner(currentMove - 1);
13558 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13559 /* to optimize, we temporarily turn off analysis mode while we undo
13560 * all the moves. Otherwise we get analysis output after each undo.
13562 if (first.analysisSupport) {
13563 SendToProgram("exit\nforce\n", &first);
13564 first.analyzing = FALSE;
13568 if (gameMode == IcsExamining && !pausing) {
13569 SendToICS(ics_prefix);
13570 SendToICS("backward 999999\n");
13572 BackwardInner(backwardMostMove);
13575 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13576 /* we have fed all the moves, so reactivate analysis mode */
13577 SendToProgram("analyze\n", &first);
13578 first.analyzing = TRUE;
13579 /*first.maybeThinking = TRUE;*/
13580 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13587 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13588 if (to >= forwardMostMove) to = forwardMostMove;
13589 if (to <= backwardMostMove) to = backwardMostMove;
13590 if (to < currentMove) {
13598 RevertEvent(Boolean annotate)
13600 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13603 if (gameMode != IcsExamining) {
13604 DisplayError(_("You are not examining a game"), 0);
13608 DisplayError(_("You can't revert while pausing"), 0);
13611 SendToICS(ics_prefix);
13612 SendToICS("revert\n");
13618 switch (gameMode) {
13619 case MachinePlaysWhite:
13620 case MachinePlaysBlack:
13621 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13622 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13625 if (forwardMostMove < 2) return;
13626 currentMove = forwardMostMove = forwardMostMove - 2;
13627 whiteTimeRemaining = timeRemaining[0][currentMove];
13628 blackTimeRemaining = timeRemaining[1][currentMove];
13629 DisplayBothClocks();
13630 DisplayMove(currentMove - 1);
13631 ClearHighlights();/*!! could figure this out*/
13632 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13633 SendToProgram("remove\n", &first);
13634 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13637 case BeginningOfGame:
13641 case IcsPlayingWhite:
13642 case IcsPlayingBlack:
13643 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13644 SendToICS(ics_prefix);
13645 SendToICS("takeback 2\n");
13647 SendToICS(ics_prefix);
13648 SendToICS("takeback 1\n");
13657 ChessProgramState *cps;
13659 switch (gameMode) {
13660 case MachinePlaysWhite:
13661 if (!WhiteOnMove(forwardMostMove)) {
13662 DisplayError(_("It is your turn"), 0);
13667 case MachinePlaysBlack:
13668 if (WhiteOnMove(forwardMostMove)) {
13669 DisplayError(_("It is your turn"), 0);
13674 case TwoMachinesPlay:
13675 if (WhiteOnMove(forwardMostMove) ==
13676 (first.twoMachinesColor[0] == 'w')) {
13682 case BeginningOfGame:
13686 SendToProgram("?\n", cps);
13690 TruncateGameEvent()
13693 if (gameMode != EditGame) return;
13700 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13701 if (forwardMostMove > currentMove) {
13702 if (gameInfo.resultDetails != NULL) {
13703 free(gameInfo.resultDetails);
13704 gameInfo.resultDetails = NULL;
13705 gameInfo.result = GameUnfinished;
13707 forwardMostMove = currentMove;
13708 HistorySet(parseList, backwardMostMove, forwardMostMove,
13716 if (appData.noChessProgram) return;
13717 switch (gameMode) {
13718 case MachinePlaysWhite:
13719 if (WhiteOnMove(forwardMostMove)) {
13720 DisplayError(_("Wait until your turn"), 0);
13724 case BeginningOfGame:
13725 case MachinePlaysBlack:
13726 if (!WhiteOnMove(forwardMostMove)) {
13727 DisplayError(_("Wait until your turn"), 0);
13732 DisplayError(_("No hint available"), 0);
13735 SendToProgram("hint\n", &first);
13736 hintRequested = TRUE;
13742 if (appData.noChessProgram) return;
13743 switch (gameMode) {
13744 case MachinePlaysWhite:
13745 if (WhiteOnMove(forwardMostMove)) {
13746 DisplayError(_("Wait until your turn"), 0);
13750 case BeginningOfGame:
13751 case MachinePlaysBlack:
13752 if (!WhiteOnMove(forwardMostMove)) {
13753 DisplayError(_("Wait until your turn"), 0);
13758 EditPositionDone(TRUE);
13760 case TwoMachinesPlay:
13765 SendToProgram("bk\n", &first);
13766 bookOutput[0] = NULLCHAR;
13767 bookRequested = TRUE;
13773 char *tags = PGNTags(&gameInfo);
13774 TagsPopUp(tags, CmailMsg());
13778 /* end button procedures */
13781 PrintPosition(fp, move)
13787 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13788 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13789 char c = PieceToChar(boards[move][i][j]);
13790 fputc(c == 'x' ? '.' : c, fp);
13791 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13794 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13795 fprintf(fp, "white to play\n");
13797 fprintf(fp, "black to play\n");
13804 if (gameInfo.white != NULL) {
13805 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13811 /* Find last component of program's own name, using some heuristics */
13813 TidyProgramName(prog, host, buf)
13814 char *prog, *host, buf[MSG_SIZ];
13817 int local = (strcmp(host, "localhost") == 0);
13818 while (!local && (p = strchr(prog, ';')) != NULL) {
13820 while (*p == ' ') p++;
13823 if (*prog == '"' || *prog == '\'') {
13824 q = strchr(prog + 1, *prog);
13826 q = strchr(prog, ' ');
13828 if (q == NULL) q = prog + strlen(prog);
13830 while (p >= prog && *p != '/' && *p != '\\') p--;
13832 if(p == prog && *p == '"') p++;
13833 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13834 memcpy(buf, p, q - p);
13835 buf[q - p] = NULLCHAR;
13843 TimeControlTagValue()
13846 if (!appData.clockMode) {
13847 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13848 } else if (movesPerSession > 0) {
13849 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13850 } else if (timeIncrement == 0) {
13851 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13853 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13855 return StrSave(buf);
13861 /* This routine is used only for certain modes */
13862 VariantClass v = gameInfo.variant;
13863 ChessMove r = GameUnfinished;
13866 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13867 r = gameInfo.result;
13868 p = gameInfo.resultDetails;
13869 gameInfo.resultDetails = NULL;
13871 ClearGameInfo(&gameInfo);
13872 gameInfo.variant = v;
13874 switch (gameMode) {
13875 case MachinePlaysWhite:
13876 gameInfo.event = StrSave( appData.pgnEventHeader );
13877 gameInfo.site = StrSave(HostName());
13878 gameInfo.date = PGNDate();
13879 gameInfo.round = StrSave("-");
13880 gameInfo.white = StrSave(first.tidy);
13881 gameInfo.black = StrSave(UserName());
13882 gameInfo.timeControl = TimeControlTagValue();
13885 case MachinePlaysBlack:
13886 gameInfo.event = StrSave( appData.pgnEventHeader );
13887 gameInfo.site = StrSave(HostName());
13888 gameInfo.date = PGNDate();
13889 gameInfo.round = StrSave("-");
13890 gameInfo.white = StrSave(UserName());
13891 gameInfo.black = StrSave(first.tidy);
13892 gameInfo.timeControl = TimeControlTagValue();
13895 case TwoMachinesPlay:
13896 gameInfo.event = StrSave( appData.pgnEventHeader );
13897 gameInfo.site = StrSave(HostName());
13898 gameInfo.date = PGNDate();
13901 snprintf(buf, MSG_SIZ, "%d", roundNr);
13902 gameInfo.round = StrSave(buf);
13904 gameInfo.round = StrSave("-");
13906 if (first.twoMachinesColor[0] == 'w') {
13907 gameInfo.white = StrSave(first.tidy);
13908 gameInfo.black = StrSave(second.tidy);
13910 gameInfo.white = StrSave(second.tidy);
13911 gameInfo.black = StrSave(first.tidy);
13913 gameInfo.timeControl = TimeControlTagValue();
13917 gameInfo.event = StrSave("Edited game");
13918 gameInfo.site = StrSave(HostName());
13919 gameInfo.date = PGNDate();
13920 gameInfo.round = StrSave("-");
13921 gameInfo.white = StrSave("-");
13922 gameInfo.black = StrSave("-");
13923 gameInfo.result = r;
13924 gameInfo.resultDetails = p;
13928 gameInfo.event = StrSave("Edited position");
13929 gameInfo.site = StrSave(HostName());
13930 gameInfo.date = PGNDate();
13931 gameInfo.round = StrSave("-");
13932 gameInfo.white = StrSave("-");
13933 gameInfo.black = StrSave("-");
13936 case IcsPlayingWhite:
13937 case IcsPlayingBlack:
13942 case PlayFromGameFile:
13943 gameInfo.event = StrSave("Game from non-PGN file");
13944 gameInfo.site = StrSave(HostName());
13945 gameInfo.date = PGNDate();
13946 gameInfo.round = StrSave("-");
13947 gameInfo.white = StrSave("?");
13948 gameInfo.black = StrSave("?");
13957 ReplaceComment(index, text)
13965 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13966 pvInfoList[index-1].depth == len &&
13967 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13968 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13969 while (*text == '\n') text++;
13970 len = strlen(text);
13971 while (len > 0 && text[len - 1] == '\n') len--;
13973 if (commentList[index] != NULL)
13974 free(commentList[index]);
13977 commentList[index] = NULL;
13980 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13981 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13982 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13983 commentList[index] = (char *) malloc(len + 2);
13984 strncpy(commentList[index], text, len);
13985 commentList[index][len] = '\n';
13986 commentList[index][len + 1] = NULLCHAR;
13988 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13990 commentList[index] = (char *) malloc(len + 7);
13991 safeStrCpy(commentList[index], "{\n", 3);
13992 safeStrCpy(commentList[index]+2, text, len+1);
13993 commentList[index][len+2] = NULLCHAR;
13994 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13995 strcat(commentList[index], "\n}\n");
14009 if (ch == '\r') continue;
14011 } while (ch != '\0');
14015 AppendComment(index, text, addBraces)
14018 Boolean addBraces; // [HGM] braces: tells if we should add {}
14023 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14024 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14027 while (*text == '\n') text++;
14028 len = strlen(text);
14029 while (len > 0 && text[len - 1] == '\n') len--;
14031 if (len == 0) return;
14033 if (commentList[index] != NULL) {
14034 old = commentList[index];
14035 oldlen = strlen(old);
14036 while(commentList[index][oldlen-1] == '\n')
14037 commentList[index][--oldlen] = NULLCHAR;
14038 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14039 safeStrCpy(commentList[index], old, oldlen + len + 6);
14041 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14042 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14043 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14044 while (*text == '\n') { text++; len--; }
14045 commentList[index][--oldlen] = NULLCHAR;
14047 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14048 else strcat(commentList[index], "\n");
14049 strcat(commentList[index], text);
14050 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14051 else strcat(commentList[index], "\n");
14053 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14055 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14056 else commentList[index][0] = NULLCHAR;
14057 strcat(commentList[index], text);
14058 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14059 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14063 static char * FindStr( char * text, char * sub_text )
14065 char * result = strstr( text, sub_text );
14067 if( result != NULL ) {
14068 result += strlen( sub_text );
14074 /* [AS] Try to extract PV info from PGN comment */
14075 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14076 char *GetInfoFromComment( int index, char * text )
14078 char * sep = text, *p;
14080 if( text != NULL && index > 0 ) {
14083 int time = -1, sec = 0, deci;
14084 char * s_eval = FindStr( text, "[%eval " );
14085 char * s_emt = FindStr( text, "[%emt " );
14087 if( s_eval != NULL || s_emt != NULL ) {
14091 if( s_eval != NULL ) {
14092 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14096 if( delim != ']' ) {
14101 if( s_emt != NULL ) {
14106 /* We expect something like: [+|-]nnn.nn/dd */
14109 if(*text != '{') return text; // [HGM] braces: must be normal comment
14111 sep = strchr( text, '/' );
14112 if( sep == NULL || sep < (text+4) ) {
14117 if(p[1] == '(') { // comment starts with PV
14118 p = strchr(p, ')'); // locate end of PV
14119 if(p == NULL || sep < p+5) return text;
14120 // at this point we have something like "{(.*) +0.23/6 ..."
14121 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14122 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14123 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14125 time = -1; sec = -1; deci = -1;
14126 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14127 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14128 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14129 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14133 if( score_lo < 0 || score_lo >= 100 ) {
14137 if(sec >= 0) time = 600*time + 10*sec; else
14138 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14140 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14142 /* [HGM] PV time: now locate end of PV info */
14143 while( *++sep >= '0' && *sep <= '9'); // strip depth
14145 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14147 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14149 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14150 while(*sep == ' ') sep++;
14161 pvInfoList[index-1].depth = depth;
14162 pvInfoList[index-1].score = score;
14163 pvInfoList[index-1].time = 10*time; // centi-sec
14164 if(*sep == '}') *sep = 0; else *--sep = '{';
14165 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14171 SendToProgram(message, cps)
14173 ChessProgramState *cps;
14175 int count, outCount, error;
14178 if (cps->pr == NULL) return;
14181 if (appData.debugMode) {
14184 fprintf(debugFP, "%ld >%-6s: %s",
14185 SubtractTimeMarks(&now, &programStartTime),
14186 cps->which, message);
14189 count = strlen(message);
14190 outCount = OutputToProcess(cps->pr, message, count, &error);
14191 if (outCount < count && !exiting
14192 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14193 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14194 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14195 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14196 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14197 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14198 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14199 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14201 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14202 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14203 gameInfo.result = res;
14205 gameInfo.resultDetails = StrSave(buf);
14207 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14208 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14213 ReceiveFromProgram(isr, closure, message, count, error)
14214 InputSourceRef isr;
14222 ChessProgramState *cps = (ChessProgramState *)closure;
14224 if (isr != cps->isr) return; /* Killed intentionally */
14227 RemoveInputSource(cps->isr);
14228 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14229 _(cps->which), cps->program);
14230 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14231 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14232 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14233 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14234 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14236 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14237 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14238 gameInfo.result = res;
14240 gameInfo.resultDetails = StrSave(buf);
14242 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14243 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14245 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14246 _(cps->which), cps->program);
14247 RemoveInputSource(cps->isr);
14249 /* [AS] Program is misbehaving badly... kill it */
14250 if( count == -2 ) {
14251 DestroyChildProcess( cps->pr, 9 );
14255 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14260 if ((end_str = strchr(message, '\r')) != NULL)
14261 *end_str = NULLCHAR;
14262 if ((end_str = strchr(message, '\n')) != NULL)
14263 *end_str = NULLCHAR;
14265 if (appData.debugMode) {
14266 TimeMark now; int print = 1;
14267 char *quote = ""; char c; int i;
14269 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14270 char start = message[0];
14271 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14272 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14273 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14274 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14275 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14276 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14277 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14278 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14279 sscanf(message, "hint: %c", &c)!=1 &&
14280 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14281 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14282 print = (appData.engineComments >= 2);
14284 message[0] = start; // restore original message
14288 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14289 SubtractTimeMarks(&now, &programStartTime), cps->which,
14295 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14296 if (appData.icsEngineAnalyze) {
14297 if (strstr(message, "whisper") != NULL ||
14298 strstr(message, "kibitz") != NULL ||
14299 strstr(message, "tellics") != NULL) return;
14302 HandleMachineMove(message, cps);
14307 SendTimeControl(cps, mps, tc, inc, sd, st)
14308 ChessProgramState *cps;
14309 int mps, inc, sd, st;
14315 if( timeControl_2 > 0 ) {
14316 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14317 tc = timeControl_2;
14320 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14321 inc /= cps->timeOdds;
14322 st /= cps->timeOdds;
14324 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14327 /* Set exact time per move, normally using st command */
14328 if (cps->stKludge) {
14329 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14331 if (seconds == 0) {
14332 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14334 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14337 snprintf(buf, MSG_SIZ, "st %d\n", st);
14340 /* Set conventional or incremental time control, using level command */
14341 if (seconds == 0) {
14342 /* Note old gnuchess bug -- minutes:seconds used to not work.
14343 Fixed in later versions, but still avoid :seconds
14344 when seconds is 0. */
14345 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14347 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14348 seconds, inc/1000.);
14351 SendToProgram(buf, cps);
14353 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14354 /* Orthogonally, limit search to given depth */
14356 if (cps->sdKludge) {
14357 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14359 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14361 SendToProgram(buf, cps);
14364 if(cps->nps >= 0) { /* [HGM] nps */
14365 if(cps->supportsNPS == FALSE)
14366 cps->nps = -1; // don't use if engine explicitly says not supported!
14368 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14369 SendToProgram(buf, cps);
14374 ChessProgramState *WhitePlayer()
14375 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14377 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14378 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14384 SendTimeRemaining(cps, machineWhite)
14385 ChessProgramState *cps;
14386 int /*boolean*/ machineWhite;
14388 char message[MSG_SIZ];
14391 /* Note: this routine must be called when the clocks are stopped
14392 or when they have *just* been set or switched; otherwise
14393 it will be off by the time since the current tick started.
14395 if (machineWhite) {
14396 time = whiteTimeRemaining / 10;
14397 otime = blackTimeRemaining / 10;
14399 time = blackTimeRemaining / 10;
14400 otime = whiteTimeRemaining / 10;
14402 /* [HGM] translate opponent's time by time-odds factor */
14403 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14404 if (appData.debugMode) {
14405 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14408 if (time <= 0) time = 1;
14409 if (otime <= 0) otime = 1;
14411 snprintf(message, MSG_SIZ, "time %ld\n", time);
14412 SendToProgram(message, cps);
14414 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14415 SendToProgram(message, cps);
14419 BoolFeature(p, name, loc, cps)
14423 ChessProgramState *cps;
14426 int len = strlen(name);
14429 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14431 sscanf(*p, "%d", &val);
14433 while (**p && **p != ' ')
14435 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14436 SendToProgram(buf, cps);
14443 IntFeature(p, name, loc, cps)
14447 ChessProgramState *cps;
14450 int len = strlen(name);
14451 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14453 sscanf(*p, "%d", loc);
14454 while (**p && **p != ' ') (*p)++;
14455 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14456 SendToProgram(buf, cps);
14463 StringFeature(p, name, loc, cps)
14467 ChessProgramState *cps;
14470 int len = strlen(name);
14471 if (strncmp((*p), name, len) == 0
14472 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14474 sscanf(*p, "%[^\"]", loc);
14475 while (**p && **p != '\"') (*p)++;
14476 if (**p == '\"') (*p)++;
14477 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14478 SendToProgram(buf, cps);
14485 ParseOption(Option *opt, ChessProgramState *cps)
14486 // [HGM] options: process the string that defines an engine option, and determine
14487 // name, type, default value, and allowed value range
14489 char *p, *q, buf[MSG_SIZ];
14490 int n, min = (-1)<<31, max = 1<<31, def;
14492 if(p = strstr(opt->name, " -spin ")) {
14493 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14494 if(max < min) max = min; // enforce consistency
14495 if(def < min) def = min;
14496 if(def > max) def = max;
14501 } else if((p = strstr(opt->name, " -slider "))) {
14502 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14503 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14504 if(max < min) max = min; // enforce consistency
14505 if(def < min) def = min;
14506 if(def > max) def = max;
14510 opt->type = Spin; // Slider;
14511 } else if((p = strstr(opt->name, " -string "))) {
14512 opt->textValue = p+9;
14513 opt->type = TextBox;
14514 } else if((p = strstr(opt->name, " -file "))) {
14515 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14516 opt->textValue = p+7;
14517 opt->type = FileName; // FileName;
14518 } else if((p = strstr(opt->name, " -path "))) {
14519 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14520 opt->textValue = p+7;
14521 opt->type = PathName; // PathName;
14522 } else if(p = strstr(opt->name, " -check ")) {
14523 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14524 opt->value = (def != 0);
14525 opt->type = CheckBox;
14526 } else if(p = strstr(opt->name, " -combo ")) {
14527 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14528 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14529 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14530 opt->value = n = 0;
14531 while(q = StrStr(q, " /// ")) {
14532 n++; *q = 0; // count choices, and null-terminate each of them
14534 if(*q == '*') { // remember default, which is marked with * prefix
14538 cps->comboList[cps->comboCnt++] = q;
14540 cps->comboList[cps->comboCnt++] = NULL;
14542 opt->type = ComboBox;
14543 } else if(p = strstr(opt->name, " -button")) {
14544 opt->type = Button;
14545 } else if(p = strstr(opt->name, " -save")) {
14546 opt->type = SaveButton;
14547 } else return FALSE;
14548 *p = 0; // terminate option name
14549 // now look if the command-line options define a setting for this engine option.
14550 if(cps->optionSettings && cps->optionSettings[0])
14551 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14552 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14553 snprintf(buf, MSG_SIZ, "option %s", p);
14554 if(p = strstr(buf, ",")) *p = 0;
14555 if(q = strchr(buf, '=')) switch(opt->type) {
14557 for(n=0; n<opt->max; n++)
14558 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14561 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14565 opt->value = atoi(q+1);
14570 SendToProgram(buf, cps);
14576 FeatureDone(cps, val)
14577 ChessProgramState* cps;
14580 DelayedEventCallback cb = GetDelayedEvent();
14581 if ((cb == InitBackEnd3 && cps == &first) ||
14582 (cb == SettingsMenuIfReady && cps == &second) ||
14583 (cb == LoadEngine) ||
14584 (cb == TwoMachinesEventIfReady)) {
14585 CancelDelayedEvent();
14586 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14588 cps->initDone = val;
14591 /* Parse feature command from engine */
14593 ParseFeatures(args, cps)
14595 ChessProgramState *cps;
14603 while (*p == ' ') p++;
14604 if (*p == NULLCHAR) return;
14606 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14607 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14608 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14609 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14610 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14611 if (BoolFeature(&p, "reuse", &val, cps)) {
14612 /* Engine can disable reuse, but can't enable it if user said no */
14613 if (!val) cps->reuse = FALSE;
14616 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14617 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14618 if (gameMode == TwoMachinesPlay) {
14619 DisplayTwoMachinesTitle();
14625 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14626 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14627 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14628 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14629 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14630 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14631 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14632 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14633 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14634 if (IntFeature(&p, "done", &val, cps)) {
14635 FeatureDone(cps, val);
14638 /* Added by Tord: */
14639 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14640 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14641 /* End of additions by Tord */
14643 /* [HGM] added features: */
14644 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14645 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14646 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14647 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14648 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14649 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14650 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14651 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14652 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14653 SendToProgram(buf, cps);
14656 if(cps->nrOptions >= MAX_OPTIONS) {
14658 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14659 DisplayError(buf, 0);
14663 /* End of additions by HGM */
14665 /* unknown feature: complain and skip */
14667 while (*q && *q != '=') q++;
14668 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14669 SendToProgram(buf, cps);
14675 while (*p && *p != '\"') p++;
14676 if (*p == '\"') p++;
14678 while (*p && *p != ' ') p++;
14686 PeriodicUpdatesEvent(newState)
14689 if (newState == appData.periodicUpdates)
14692 appData.periodicUpdates=newState;
14694 /* Display type changes, so update it now */
14695 // DisplayAnalysis();
14697 /* Get the ball rolling again... */
14699 AnalysisPeriodicEvent(1);
14700 StartAnalysisClock();
14705 PonderNextMoveEvent(newState)
14708 if (newState == appData.ponderNextMove) return;
14709 if (gameMode == EditPosition) EditPositionDone(TRUE);
14711 SendToProgram("hard\n", &first);
14712 if (gameMode == TwoMachinesPlay) {
14713 SendToProgram("hard\n", &second);
14716 SendToProgram("easy\n", &first);
14717 thinkOutput[0] = NULLCHAR;
14718 if (gameMode == TwoMachinesPlay) {
14719 SendToProgram("easy\n", &second);
14722 appData.ponderNextMove = newState;
14726 NewSettingEvent(option, feature, command, value)
14728 int option, value, *feature;
14732 if (gameMode == EditPosition) EditPositionDone(TRUE);
14733 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14734 if(feature == NULL || *feature) SendToProgram(buf, &first);
14735 if (gameMode == TwoMachinesPlay) {
14736 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14741 ShowThinkingEvent()
14742 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14744 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14745 int newState = appData.showThinking
14746 // [HGM] thinking: other features now need thinking output as well
14747 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14749 if (oldState == newState) return;
14750 oldState = newState;
14751 if (gameMode == EditPosition) EditPositionDone(TRUE);
14753 SendToProgram("post\n", &first);
14754 if (gameMode == TwoMachinesPlay) {
14755 SendToProgram("post\n", &second);
14758 SendToProgram("nopost\n", &first);
14759 thinkOutput[0] = NULLCHAR;
14760 if (gameMode == TwoMachinesPlay) {
14761 SendToProgram("nopost\n", &second);
14764 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14768 AskQuestionEvent(title, question, replyPrefix, which)
14769 char *title; char *question; char *replyPrefix; char *which;
14771 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14772 if (pr == NoProc) return;
14773 AskQuestion(title, question, replyPrefix, pr);
14777 TypeInEvent(char firstChar)
14779 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14780 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14781 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14782 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14783 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14784 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14785 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14786 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14787 gameMode == Training) PopUpMoveDialog(firstChar);
14791 TypeInDoneEvent(char *move)
14794 int n, fromX, fromY, toX, toY;
14796 ChessMove moveType;
\r
14799 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14800 EditPositionPasteFEN(move);
\r
14803 // [HGM] movenum: allow move number to be typed in any mode
\r
14804 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14805 ToNrEvent(2*n-1);
\r
14809 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14810 gameMode != Training) {
\r
14811 DisplayMoveError(_("Displayed move is not current"));
\r
14813 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14814 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14815 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14816 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14817 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14818 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14820 DisplayMoveError(_("Could not parse move"));
\r
14826 DisplayMove(moveNumber)
14829 char message[MSG_SIZ];
14831 char cpThinkOutput[MSG_SIZ];
14833 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14835 if (moveNumber == forwardMostMove - 1 ||
14836 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14838 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14840 if (strchr(cpThinkOutput, '\n')) {
14841 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14844 *cpThinkOutput = NULLCHAR;
14847 /* [AS] Hide thinking from human user */
14848 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14849 *cpThinkOutput = NULLCHAR;
14850 if( thinkOutput[0] != NULLCHAR ) {
14853 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14854 cpThinkOutput[i] = '.';
14856 cpThinkOutput[i] = NULLCHAR;
14857 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14861 if (moveNumber == forwardMostMove - 1 &&
14862 gameInfo.resultDetails != NULL) {
14863 if (gameInfo.resultDetails[0] == NULLCHAR) {
14864 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14866 snprintf(res, MSG_SIZ, " {%s} %s",
14867 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14873 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14874 DisplayMessage(res, cpThinkOutput);
14876 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14877 WhiteOnMove(moveNumber) ? " " : ".. ",
14878 parseList[moveNumber], res);
14879 DisplayMessage(message, cpThinkOutput);
14884 DisplayComment(moveNumber, text)
14888 char title[MSG_SIZ];
14889 char buf[8000]; // comment can be long!
14892 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14893 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14895 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14896 WhiteOnMove(moveNumber) ? " " : ".. ",
14897 parseList[moveNumber]);
14899 // [HGM] PV info: display PV info together with (or as) comment
14900 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14901 if(text == NULL) text = "";
14902 score = pvInfoList[moveNumber].score;
14903 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14904 depth, (pvInfoList[moveNumber].time+50)/100, text);
14907 if (text != NULL && (appData.autoDisplayComment || commentUp))
14908 CommentPopUp(title, text);
14911 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14912 * might be busy thinking or pondering. It can be omitted if your
14913 * gnuchess is configured to stop thinking immediately on any user
14914 * input. However, that gnuchess feature depends on the FIONREAD
14915 * ioctl, which does not work properly on some flavors of Unix.
14919 ChessProgramState *cps;
14922 if (!cps->useSigint) return;
14923 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14924 switch (gameMode) {
14925 case MachinePlaysWhite:
14926 case MachinePlaysBlack:
14927 case TwoMachinesPlay:
14928 case IcsPlayingWhite:
14929 case IcsPlayingBlack:
14932 /* Skip if we know it isn't thinking */
14933 if (!cps->maybeThinking) return;
14934 if (appData.debugMode)
14935 fprintf(debugFP, "Interrupting %s\n", cps->which);
14936 InterruptChildProcess(cps->pr);
14937 cps->maybeThinking = FALSE;
14942 #endif /*ATTENTION*/
14948 if (whiteTimeRemaining <= 0) {
14951 if (appData.icsActive) {
14952 if (appData.autoCallFlag &&
14953 gameMode == IcsPlayingBlack && !blackFlag) {
14954 SendToICS(ics_prefix);
14955 SendToICS("flag\n");
14959 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14961 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14962 if (appData.autoCallFlag) {
14963 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14970 if (blackTimeRemaining <= 0) {
14973 if (appData.icsActive) {
14974 if (appData.autoCallFlag &&
14975 gameMode == IcsPlayingWhite && !whiteFlag) {
14976 SendToICS(ics_prefix);
14977 SendToICS("flag\n");
14981 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14983 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14984 if (appData.autoCallFlag) {
14985 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14998 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14999 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15002 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15004 if ( !WhiteOnMove(forwardMostMove) ) {
15005 /* White made time control */
15006 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15007 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15008 /* [HGM] time odds: correct new time quota for time odds! */
15009 / WhitePlayer()->timeOdds;
15010 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15012 lastBlack -= blackTimeRemaining;
15013 /* Black made time control */
15014 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15015 / WhitePlayer()->other->timeOdds;
15016 lastWhite = whiteTimeRemaining;
15021 DisplayBothClocks()
15023 int wom = gameMode == EditPosition ?
15024 !blackPlaysFirst : WhiteOnMove(currentMove);
15025 DisplayWhiteClock(whiteTimeRemaining, wom);
15026 DisplayBlackClock(blackTimeRemaining, !wom);
15030 /* Timekeeping seems to be a portability nightmare. I think everyone
15031 has ftime(), but I'm really not sure, so I'm including some ifdefs
15032 to use other calls if you don't. Clocks will be less accurate if
15033 you have neither ftime nor gettimeofday.
15036 /* VS 2008 requires the #include outside of the function */
15037 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15038 #include <sys/timeb.h>
15041 /* Get the current time as a TimeMark */
15046 #if HAVE_GETTIMEOFDAY
15048 struct timeval timeVal;
15049 struct timezone timeZone;
15051 gettimeofday(&timeVal, &timeZone);
15052 tm->sec = (long) timeVal.tv_sec;
15053 tm->ms = (int) (timeVal.tv_usec / 1000L);
15055 #else /*!HAVE_GETTIMEOFDAY*/
15058 // include <sys/timeb.h> / moved to just above start of function
15059 struct timeb timeB;
15062 tm->sec = (long) timeB.time;
15063 tm->ms = (int) timeB.millitm;
15065 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15066 tm->sec = (long) time(NULL);
15072 /* Return the difference in milliseconds between two
15073 time marks. We assume the difference will fit in a long!
15076 SubtractTimeMarks(tm2, tm1)
15077 TimeMark *tm2, *tm1;
15079 return 1000L*(tm2->sec - tm1->sec) +
15080 (long) (tm2->ms - tm1->ms);
15085 * Code to manage the game clocks.
15087 * In tournament play, black starts the clock and then white makes a move.
15088 * We give the human user a slight advantage if he is playing white---the
15089 * clocks don't run until he makes his first move, so it takes zero time.
15090 * Also, we don't account for network lag, so we could get out of sync
15091 * with GNU Chess's clock -- but then, referees are always right.
15094 static TimeMark tickStartTM;
15095 static long intendedTickLength;
15098 NextTickLength(timeRemaining)
15099 long timeRemaining;
15101 long nominalTickLength, nextTickLength;
15103 if (timeRemaining > 0L && timeRemaining <= 10000L)
15104 nominalTickLength = 100L;
15106 nominalTickLength = 1000L;
15107 nextTickLength = timeRemaining % nominalTickLength;
15108 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15110 return nextTickLength;
15113 /* Adjust clock one minute up or down */
15115 AdjustClock(Boolean which, int dir)
15117 if(which) blackTimeRemaining += 60000*dir;
15118 else whiteTimeRemaining += 60000*dir;
15119 DisplayBothClocks();
15122 /* Stop clocks and reset to a fresh time control */
15126 (void) StopClockTimer();
15127 if (appData.icsActive) {
15128 whiteTimeRemaining = blackTimeRemaining = 0;
15129 } else if (searchTime) {
15130 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15131 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15132 } else { /* [HGM] correct new time quote for time odds */
15133 whiteTC = blackTC = fullTimeControlString;
15134 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15135 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15137 if (whiteFlag || blackFlag) {
15139 whiteFlag = blackFlag = FALSE;
15141 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15142 DisplayBothClocks();
15145 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15147 /* Decrement running clock by amount of time that has passed */
15151 long timeRemaining;
15152 long lastTickLength, fudge;
15155 if (!appData.clockMode) return;
15156 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15160 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15162 /* Fudge if we woke up a little too soon */
15163 fudge = intendedTickLength - lastTickLength;
15164 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15166 if (WhiteOnMove(forwardMostMove)) {
15167 if(whiteNPS >= 0) lastTickLength = 0;
15168 timeRemaining = whiteTimeRemaining -= lastTickLength;
15169 if(timeRemaining < 0 && !appData.icsActive) {
15170 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15171 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15172 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15173 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15176 DisplayWhiteClock(whiteTimeRemaining - fudge,
15177 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15179 if(blackNPS >= 0) lastTickLength = 0;
15180 timeRemaining = blackTimeRemaining -= lastTickLength;
15181 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15182 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15184 blackStartMove = forwardMostMove;
15185 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15188 DisplayBlackClock(blackTimeRemaining - fudge,
15189 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15191 if (CheckFlags()) return;
15194 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15195 StartClockTimer(intendedTickLength);
15197 /* if the time remaining has fallen below the alarm threshold, sound the
15198 * alarm. if the alarm has sounded and (due to a takeback or time control
15199 * with increment) the time remaining has increased to a level above the
15200 * threshold, reset the alarm so it can sound again.
15203 if (appData.icsActive && appData.icsAlarm) {
15205 /* make sure we are dealing with the user's clock */
15206 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15207 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15210 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15211 alarmSounded = FALSE;
15212 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15214 alarmSounded = TRUE;
15220 /* A player has just moved, so stop the previously running
15221 clock and (if in clock mode) start the other one.
15222 We redisplay both clocks in case we're in ICS mode, because
15223 ICS gives us an update to both clocks after every move.
15224 Note that this routine is called *after* forwardMostMove
15225 is updated, so the last fractional tick must be subtracted
15226 from the color that is *not* on move now.
15229 SwitchClocks(int newMoveNr)
15231 long lastTickLength;
15233 int flagged = FALSE;
15237 if (StopClockTimer() && appData.clockMode) {
15238 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15239 if (!WhiteOnMove(forwardMostMove)) {
15240 if(blackNPS >= 0) lastTickLength = 0;
15241 blackTimeRemaining -= lastTickLength;
15242 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15243 // if(pvInfoList[forwardMostMove].time == -1)
15244 pvInfoList[forwardMostMove].time = // use GUI time
15245 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15247 if(whiteNPS >= 0) lastTickLength = 0;
15248 whiteTimeRemaining -= lastTickLength;
15249 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15250 // if(pvInfoList[forwardMostMove].time == -1)
15251 pvInfoList[forwardMostMove].time =
15252 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15254 flagged = CheckFlags();
15256 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15257 CheckTimeControl();
15259 if (flagged || !appData.clockMode) return;
15261 switch (gameMode) {
15262 case MachinePlaysBlack:
15263 case MachinePlaysWhite:
15264 case BeginningOfGame:
15265 if (pausing) return;
15269 case PlayFromGameFile:
15277 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15278 if(WhiteOnMove(forwardMostMove))
15279 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15280 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15284 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15285 whiteTimeRemaining : blackTimeRemaining);
15286 StartClockTimer(intendedTickLength);
15290 /* Stop both clocks */
15294 long lastTickLength;
15297 if (!StopClockTimer()) return;
15298 if (!appData.clockMode) return;
15302 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15303 if (WhiteOnMove(forwardMostMove)) {
15304 if(whiteNPS >= 0) lastTickLength = 0;
15305 whiteTimeRemaining -= lastTickLength;
15306 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15308 if(blackNPS >= 0) lastTickLength = 0;
15309 blackTimeRemaining -= lastTickLength;
15310 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15315 /* Start clock of player on move. Time may have been reset, so
15316 if clock is already running, stop and restart it. */
15320 (void) StopClockTimer(); /* in case it was running already */
15321 DisplayBothClocks();
15322 if (CheckFlags()) return;
15324 if (!appData.clockMode) return;
15325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15327 GetTimeMark(&tickStartTM);
15328 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15329 whiteTimeRemaining : blackTimeRemaining);
15331 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15332 whiteNPS = blackNPS = -1;
15333 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15334 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15335 whiteNPS = first.nps;
15336 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15337 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15338 blackNPS = first.nps;
15339 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15340 whiteNPS = second.nps;
15341 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15342 blackNPS = second.nps;
15343 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15345 StartClockTimer(intendedTickLength);
15352 long second, minute, hour, day;
15354 static char buf[32];
15356 if (ms > 0 && ms <= 9900) {
15357 /* convert milliseconds to tenths, rounding up */
15358 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15360 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15364 /* convert milliseconds to seconds, rounding up */
15365 /* use floating point to avoid strangeness of integer division
15366 with negative dividends on many machines */
15367 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15374 day = second / (60 * 60 * 24);
15375 second = second % (60 * 60 * 24);
15376 hour = second / (60 * 60);
15377 second = second % (60 * 60);
15378 minute = second / 60;
15379 second = second % 60;
15382 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15383 sign, day, hour, minute, second);
15385 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15387 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15394 * This is necessary because some C libraries aren't ANSI C compliant yet.
15397 StrStr(string, match)
15398 char *string, *match;
15402 length = strlen(match);
15404 for (i = strlen(string) - length; i >= 0; i--, string++)
15405 if (!strncmp(match, string, length))
15412 StrCaseStr(string, match)
15413 char *string, *match;
15417 length = strlen(match);
15419 for (i = strlen(string) - length; i >= 0; i--, string++) {
15420 for (j = 0; j < length; j++) {
15421 if (ToLower(match[j]) != ToLower(string[j]))
15424 if (j == length) return string;
15438 c1 = ToLower(*s1++);
15439 c2 = ToLower(*s2++);
15440 if (c1 > c2) return 1;
15441 if (c1 < c2) return -1;
15442 if (c1 == NULLCHAR) return 0;
15451 return isupper(c) ? tolower(c) : c;
15459 return islower(c) ? toupper(c) : c;
15461 #endif /* !_amigados */
15469 if ((ret = (char *) malloc(strlen(s) + 1)))
15471 safeStrCpy(ret, s, strlen(s)+1);
15477 StrSavePtr(s, savePtr)
15478 char *s, **savePtr;
15483 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15484 safeStrCpy(*savePtr, s, strlen(s)+1);
15496 clock = time((time_t *)NULL);
15497 tm = localtime(&clock);
15498 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15499 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15500 return StrSave(buf);
15505 PositionToFEN(move, overrideCastling)
15507 char *overrideCastling;
15509 int i, j, fromX, fromY, toX, toY;
15516 whiteToPlay = (gameMode == EditPosition) ?
15517 !blackPlaysFirst : (move % 2 == 0);
15520 /* Piece placement data */
15521 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15523 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15524 if (boards[move][i][j] == EmptySquare) {
15526 } else { ChessSquare piece = boards[move][i][j];
15527 if (emptycount > 0) {
15528 if(emptycount<10) /* [HGM] can be >= 10 */
15529 *p++ = '0' + emptycount;
15530 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15533 if(PieceToChar(piece) == '+') {
15534 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15536 piece = (ChessSquare)(DEMOTED piece);
15538 *p++ = PieceToChar(piece);
15540 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15541 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15546 if (emptycount > 0) {
15547 if(emptycount<10) /* [HGM] can be >= 10 */
15548 *p++ = '0' + emptycount;
15549 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15556 /* [HGM] print Crazyhouse or Shogi holdings */
15557 if( gameInfo.holdingsWidth ) {
15558 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15560 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15561 piece = boards[move][i][BOARD_WIDTH-1];
15562 if( piece != EmptySquare )
15563 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15564 *p++ = PieceToChar(piece);
15566 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15567 piece = boards[move][BOARD_HEIGHT-i-1][0];
15568 if( piece != EmptySquare )
15569 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15570 *p++ = PieceToChar(piece);
15573 if( q == p ) *p++ = '-';
15579 *p++ = whiteToPlay ? 'w' : 'b';
15582 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15583 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15585 if(nrCastlingRights) {
15587 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15588 /* [HGM] write directly from rights */
15589 if(boards[move][CASTLING][2] != NoRights &&
15590 boards[move][CASTLING][0] != NoRights )
15591 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15592 if(boards[move][CASTLING][2] != NoRights &&
15593 boards[move][CASTLING][1] != NoRights )
15594 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15595 if(boards[move][CASTLING][5] != NoRights &&
15596 boards[move][CASTLING][3] != NoRights )
15597 *p++ = boards[move][CASTLING][3] + AAA;
15598 if(boards[move][CASTLING][5] != NoRights &&
15599 boards[move][CASTLING][4] != NoRights )
15600 *p++ = boards[move][CASTLING][4] + AAA;
15603 /* [HGM] write true castling rights */
15604 if( nrCastlingRights == 6 ) {
15605 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15606 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15607 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15608 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15609 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15610 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15611 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15612 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15615 if (q == p) *p++ = '-'; /* No castling rights */
15619 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15620 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15621 /* En passant target square */
15622 if (move > backwardMostMove) {
15623 fromX = moveList[move - 1][0] - AAA;
15624 fromY = moveList[move - 1][1] - ONE;
15625 toX = moveList[move - 1][2] - AAA;
15626 toY = moveList[move - 1][3] - ONE;
15627 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15628 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15629 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15631 /* 2-square pawn move just happened */
15633 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15637 } else if(move == backwardMostMove) {
15638 // [HGM] perhaps we should always do it like this, and forget the above?
15639 if((signed char)boards[move][EP_STATUS] >= 0) {
15640 *p++ = boards[move][EP_STATUS] + AAA;
15641 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15652 /* [HGM] find reversible plies */
15653 { int i = 0, j=move;
15655 if (appData.debugMode) { int k;
15656 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15657 for(k=backwardMostMove; k<=forwardMostMove; k++)
15658 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15662 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15663 if( j == backwardMostMove ) i += initialRulePlies;
15664 sprintf(p, "%d ", i);
15665 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15667 /* Fullmove number */
15668 sprintf(p, "%d", (move / 2) + 1);
15670 return StrSave(buf);
15674 ParseFEN(board, blackPlaysFirst, fen)
15676 int *blackPlaysFirst;
15686 /* [HGM] by default clear Crazyhouse holdings, if present */
15687 if(gameInfo.holdingsWidth) {
15688 for(i=0; i<BOARD_HEIGHT; i++) {
15689 board[i][0] = EmptySquare; /* black holdings */
15690 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15691 board[i][1] = (ChessSquare) 0; /* black counts */
15692 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15696 /* Piece placement data */
15697 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15700 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15701 if (*p == '/') p++;
15702 emptycount = gameInfo.boardWidth - j;
15703 while (emptycount--)
15704 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15706 #if(BOARD_FILES >= 10)
15707 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15708 p++; emptycount=10;
15709 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15710 while (emptycount--)
15711 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15713 } else if (isdigit(*p)) {
15714 emptycount = *p++ - '0';
15715 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15716 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15717 while (emptycount--)
15718 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15719 } else if (*p == '+' || isalpha(*p)) {
15720 if (j >= gameInfo.boardWidth) return FALSE;
15722 piece = CharToPiece(*++p);
15723 if(piece == EmptySquare) return FALSE; /* unknown piece */
15724 piece = (ChessSquare) (PROMOTED piece ); p++;
15725 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15726 } else piece = CharToPiece(*p++);
15728 if(piece==EmptySquare) return FALSE; /* unknown piece */
15729 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15730 piece = (ChessSquare) (PROMOTED piece);
15731 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15734 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15740 while (*p == '/' || *p == ' ') p++;
15742 /* [HGM] look for Crazyhouse holdings here */
15743 while(*p==' ') p++;
15744 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15746 if(*p == '-' ) p++; /* empty holdings */ else {
15747 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15748 /* if we would allow FEN reading to set board size, we would */
15749 /* have to add holdings and shift the board read so far here */
15750 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15752 if((int) piece >= (int) BlackPawn ) {
15753 i = (int)piece - (int)BlackPawn;
15754 i = PieceToNumber((ChessSquare)i);
15755 if( i >= gameInfo.holdingsSize ) return FALSE;
15756 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15757 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15759 i = (int)piece - (int)WhitePawn;
15760 i = PieceToNumber((ChessSquare)i);
15761 if( i >= gameInfo.holdingsSize ) return FALSE;
15762 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15763 board[i][BOARD_WIDTH-2]++; /* black holdings */
15770 while(*p == ' ') p++;
15774 if(appData.colorNickNames) {
15775 if( c == appData.colorNickNames[0] ) c = 'w'; else
15776 if( c == appData.colorNickNames[1] ) c = 'b';
15780 *blackPlaysFirst = FALSE;
15783 *blackPlaysFirst = TRUE;
15789 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15790 /* return the extra info in global variiables */
15792 /* set defaults in case FEN is incomplete */
15793 board[EP_STATUS] = EP_UNKNOWN;
15794 for(i=0; i<nrCastlingRights; i++ ) {
15795 board[CASTLING][i] =
15796 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15797 } /* assume possible unless obviously impossible */
15798 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15799 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15800 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15801 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15802 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15803 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15804 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15805 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15808 while(*p==' ') p++;
15809 if(nrCastlingRights) {
15810 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15811 /* castling indicator present, so default becomes no castlings */
15812 for(i=0; i<nrCastlingRights; i++ ) {
15813 board[CASTLING][i] = NoRights;
15816 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15817 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15818 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15819 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15820 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15822 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15823 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15824 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15826 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15827 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15828 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15829 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15830 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15831 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15834 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15835 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15836 board[CASTLING][2] = whiteKingFile;
15839 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15840 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15841 board[CASTLING][2] = whiteKingFile;
15844 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15845 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15846 board[CASTLING][5] = blackKingFile;
15849 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15850 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15851 board[CASTLING][5] = blackKingFile;
15854 default: /* FRC castlings */
15855 if(c >= 'a') { /* black rights */
15856 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15857 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15858 if(i == BOARD_RGHT) break;
15859 board[CASTLING][5] = i;
15861 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15862 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15864 board[CASTLING][3] = c;
15866 board[CASTLING][4] = c;
15867 } else { /* white rights */
15868 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15869 if(board[0][i] == WhiteKing) break;
15870 if(i == BOARD_RGHT) break;
15871 board[CASTLING][2] = i;
15872 c -= AAA - 'a' + 'A';
15873 if(board[0][c] >= WhiteKing) break;
15875 board[CASTLING][0] = c;
15877 board[CASTLING][1] = c;
15881 for(i=0; i<nrCastlingRights; i++)
15882 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15883 if (appData.debugMode) {
15884 fprintf(debugFP, "FEN castling rights:");
15885 for(i=0; i<nrCastlingRights; i++)
15886 fprintf(debugFP, " %d", board[CASTLING][i]);
15887 fprintf(debugFP, "\n");
15890 while(*p==' ') p++;
15893 /* read e.p. field in games that know e.p. capture */
15894 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15895 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15897 p++; board[EP_STATUS] = EP_NONE;
15899 char c = *p++ - AAA;
15901 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15902 if(*p >= '0' && *p <='9') p++;
15903 board[EP_STATUS] = c;
15908 if(sscanf(p, "%d", &i) == 1) {
15909 FENrulePlies = i; /* 50-move ply counter */
15910 /* (The move number is still ignored) */
15917 EditPositionPasteFEN(char *fen)
15920 Board initial_position;
15922 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15923 DisplayError(_("Bad FEN position in clipboard"), 0);
15926 int savedBlackPlaysFirst = blackPlaysFirst;
15927 EditPositionEvent();
15928 blackPlaysFirst = savedBlackPlaysFirst;
15929 CopyBoard(boards[0], initial_position);
15930 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15931 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15932 DisplayBothClocks();
15933 DrawPosition(FALSE, boards[currentMove]);
15938 static char cseq[12] = "\\ ";
15940 Boolean set_cont_sequence(char *new_seq)
15945 // handle bad attempts to set the sequence
15947 return 0; // acceptable error - no debug
15949 len = strlen(new_seq);
15950 ret = (len > 0) && (len < sizeof(cseq));
15952 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15953 else if (appData.debugMode)
15954 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15959 reformat a source message so words don't cross the width boundary. internal
15960 newlines are not removed. returns the wrapped size (no null character unless
15961 included in source message). If dest is NULL, only calculate the size required
15962 for the dest buffer. lp argument indicats line position upon entry, and it's
15963 passed back upon exit.
15965 int wrap(char *dest, char *src, int count, int width, int *lp)
15967 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15969 cseq_len = strlen(cseq);
15970 old_line = line = *lp;
15971 ansi = len = clen = 0;
15973 for (i=0; i < count; i++)
15975 if (src[i] == '\033')
15978 // if we hit the width, back up
15979 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15981 // store i & len in case the word is too long
15982 old_i = i, old_len = len;
15984 // find the end of the last word
15985 while (i && src[i] != ' ' && src[i] != '\n')
15991 // word too long? restore i & len before splitting it
15992 if ((old_i-i+clen) >= width)
15999 if (i && src[i-1] == ' ')
16002 if (src[i] != ' ' && src[i] != '\n')
16009 // now append the newline and continuation sequence
16014 strncpy(dest+len, cseq, cseq_len);
16022 dest[len] = src[i];
16026 if (src[i] == '\n')
16031 if (dest && appData.debugMode)
16033 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16034 count, width, line, len, *lp);
16035 show_bytes(debugFP, src, count);
16036 fprintf(debugFP, "\ndest: ");
16037 show_bytes(debugFP, dest, len);
16038 fprintf(debugFP, "\n");
16040 *lp = dest ? line : old_line;
16045 // [HGM] vari: routines for shelving variations
16048 PushTail(int firstMove, int lastMove)
16050 int i, j, nrMoves = lastMove - firstMove;
16052 if(appData.icsActive) { // only in local mode
16053 forwardMostMove = currentMove; // mimic old ICS behavior
16056 if(storedGames >= MAX_VARIATIONS-1) return;
16058 // push current tail of game on stack
16059 savedResult[storedGames] = gameInfo.result;
16060 savedDetails[storedGames] = gameInfo.resultDetails;
16061 gameInfo.resultDetails = NULL;
16062 savedFirst[storedGames] = firstMove;
16063 savedLast [storedGames] = lastMove;
16064 savedFramePtr[storedGames] = framePtr;
16065 framePtr -= nrMoves; // reserve space for the boards
16066 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16067 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16068 for(j=0; j<MOVE_LEN; j++)
16069 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16070 for(j=0; j<2*MOVE_LEN; j++)
16071 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16072 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16073 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16074 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16075 pvInfoList[firstMove+i-1].depth = 0;
16076 commentList[framePtr+i] = commentList[firstMove+i];
16077 commentList[firstMove+i] = NULL;
16081 forwardMostMove = firstMove; // truncate game so we can start variation
16082 if(storedGames == 1) GreyRevert(FALSE);
16086 PopTail(Boolean annotate)
16089 char buf[8000], moveBuf[20];
16091 if(appData.icsActive) return FALSE; // only in local mode
16092 if(!storedGames) return FALSE; // sanity
16093 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16096 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16097 nrMoves = savedLast[storedGames] - currentMove;
16100 if(!WhiteOnMove(currentMove))
16101 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16102 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16103 for(i=currentMove; i<forwardMostMove; i++) {
16105 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16106 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16107 strcat(buf, moveBuf);
16108 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16109 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16113 for(i=1; i<=nrMoves; i++) { // copy last variation back
16114 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16115 for(j=0; j<MOVE_LEN; j++)
16116 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16117 for(j=0; j<2*MOVE_LEN; j++)
16118 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16119 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16120 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16121 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16122 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16123 commentList[currentMove+i] = commentList[framePtr+i];
16124 commentList[framePtr+i] = NULL;
16126 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16127 framePtr = savedFramePtr[storedGames];
16128 gameInfo.result = savedResult[storedGames];
16129 if(gameInfo.resultDetails != NULL) {
16130 free(gameInfo.resultDetails);
16132 gameInfo.resultDetails = savedDetails[storedGames];
16133 forwardMostMove = currentMove + nrMoves;
16134 if(storedGames == 0) GreyRevert(TRUE);
16140 { // remove all shelved variations
16142 for(i=0; i<storedGames; i++) {
16143 if(savedDetails[i])
16144 free(savedDetails[i]);
16145 savedDetails[i] = NULL;
16147 for(i=framePtr; i<MAX_MOVES; i++) {
16148 if(commentList[i]) free(commentList[i]);
16149 commentList[i] = NULL;
16151 framePtr = MAX_MOVES-1;
16156 LoadVariation(int index, char *text)
16157 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16158 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16159 int level = 0, move;
16161 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16162 // first find outermost bracketing variation
16163 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16164 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16165 if(*p == '{') wait = '}'; else
16166 if(*p == '[') wait = ']'; else
16167 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16168 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16170 if(*p == wait) wait = NULLCHAR; // closing ]} found
16173 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16174 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16175 end[1] = NULLCHAR; // clip off comment beyond variation
16176 ToNrEvent(currentMove-1);
16177 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16178 // kludge: use ParsePV() to append variation to game
16179 move = currentMove;
16180 ParsePV(start, TRUE);
16181 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16182 ClearPremoveHighlights();
16184 ToNrEvent(currentMove+1);