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 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) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
246 Boolean partnerBoardValid = 0;
247 char partnerStatus[MSG_SIZ];
249 Boolean originalFlip;
250 Boolean twoBoards = 0;
251 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
252 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
253 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
254 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
255 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
256 int opponentKibitzes;
257 int lastSavedGame; /* [HGM] save: ID of game */
258 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
259 extern int chatCount;
261 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
263 /* States for ics_getting_history */
265 #define H_REQUESTED 1
266 #define H_GOT_REQ_HEADER 2
267 #define H_GOT_UNREQ_HEADER 3
268 #define H_GETTING_MOVES 4
269 #define H_GOT_UNWANTED_HEADER 5
271 /* whosays values for GameEnds */
280 /* Maximum number of games in a cmail message */
281 #define CMAIL_MAX_GAMES 20
283 /* Different types of move when calling RegisterMove */
285 #define CMAIL_RESIGN 1
287 #define CMAIL_ACCEPT 3
289 /* Different types of result to remember for each game */
290 #define CMAIL_NOT_RESULT 0
291 #define CMAIL_OLD_RESULT 1
292 #define CMAIL_NEW_RESULT 2
294 /* Telnet protocol constants */
305 static char * safeStrCpy( char * dst, const char * src, size_t count )
307 assert( dst != NULL );
308 assert( src != NULL );
311 strncpy( dst, src, count );
312 dst[ count-1 ] = '\0';
316 /* Some compiler can't cast u64 to double
317 * This function do the job for us:
319 * We use the highest bit for cast, this only
320 * works if the highest bit is not
321 * in use (This should not happen)
323 * We used this for all compiler
326 u64ToDouble(u64 value)
329 u64 tmp = value & u64Const(0x7fffffffffffffff);
330 r = (double)(s64)tmp;
331 if (value & u64Const(0x8000000000000000))
332 r += 9.2233720368547758080e18; /* 2^63 */
336 /* Fake up flags for now, as we aren't keeping track of castling
337 availability yet. [HGM] Change of logic: the flag now only
338 indicates the type of castlings allowed by the rule of the game.
339 The actual rights themselves are maintained in the array
340 castlingRights, as part of the game history, and are not probed
346 int flags = F_ALL_CASTLE_OK;
347 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
348 switch (gameInfo.variant) {
350 flags &= ~F_ALL_CASTLE_OK;
351 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
352 flags |= F_IGNORE_CHECK;
354 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
357 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359 case VariantKriegspiel:
360 flags |= F_KRIEGSPIEL_CAPTURE;
362 case VariantCapaRandom:
363 case VariantFischeRandom:
364 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
365 case VariantNoCastle:
366 case VariantShatranj:
369 flags &= ~F_ALL_CASTLE_OK;
377 FILE *gameFileFP, *debugFP;
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
381 into a fixed-size buffer. Because of this, we must be prepared to
382 receive strings as long as the size of the input buffer, which is currently
383 set to 4K for Windows and 8K for the rest.
384 So, we must either allocate sufficiently large buffers here, or
385 reduce the size of the input buffer in the input reading part.
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
390 char thinkOutput1[MSG_SIZ*10];
392 ChessProgramState first, second;
394 /* premove variables */
397 int premoveFromX = 0;
398 int premoveFromY = 0;
399 int premovePromoChar = 0;
401 Boolean alarmSounded;
402 /* end premove variables */
404 char *ics_prefix = "$";
405 int ics_type = ICS_GENERIC;
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
408 int pauseExamForwardMostMove = 0;
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
415 int whiteFlag = FALSE, blackFlag = FALSE;
416 int userOfferedDraw = FALSE;
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
419 int cmailMoveType[CMAIL_MAX_GAMES];
420 long ics_clock_paused = 0;
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
423 GameMode gameMode = BeginningOfGame;
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
427 int hiddenThinkOutputState = 0; /* [AS] */
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
429 int adjudicateLossPlies = 6;
430 char white_holding[64], black_holding[64];
431 TimeMark lastNodeCountTime;
432 long lastNodeCount=0;
433 int have_sent_ICS_logon = 0;
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
436 long timeControl_2; /* [AS] Allow separate time controls */
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
438 long timeRemaining[2][MAX_MOVES];
440 TimeMark programStartTime;
441 char ics_handle[MSG_SIZ];
442 int have_set_title = 0;
444 /* animateTraining preserves the state of appData.animate
445 * when Training mode is activated. This allows the
446 * response to be animated when appData.animate == TRUE and
447 * appData.animateDragging == TRUE.
449 Boolean animateTraining;
455 Board boards[MAX_MOVES];
456 /* [HGM] Following 7 needed for accurate legality tests: */
457 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
458 signed char initialRights[BOARD_FILES];
459 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
460 int initialRulePlies, FENrulePlies;
461 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
464 int mute; // mute all sounds
466 // [HGM] vari: next 12 to save and restore variations
467 #define MAX_VARIATIONS 10
468 int framePtr = MAX_MOVES-1; // points to free stack entry
470 int savedFirst[MAX_VARIATIONS];
471 int savedLast[MAX_VARIATIONS];
472 int savedFramePtr[MAX_VARIATIONS];
473 char *savedDetails[MAX_VARIATIONS];
474 ChessMove savedResult[MAX_VARIATIONS];
476 void PushTail P((int firstMove, int lastMove));
477 Boolean PopTail P((Boolean annotate));
478 void CleanupTail P((void));
480 ChessSquare FIDEArray[2][BOARD_FILES] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackBishop, BlackKnight, BlackRook }
487 ChessSquare twoKingsArray[2][BOARD_FILES] = {
488 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
489 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
490 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
491 BlackKing, BlackKing, BlackKnight, BlackRook }
494 ChessSquare KnightmateArray[2][BOARD_FILES] = {
495 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
496 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
497 { BlackRook, BlackMan, BlackBishop, BlackQueen,
498 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
501 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
505 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
508 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
510 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
512 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
516 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
517 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackMan, BlackFerz,
519 BlackKing, BlackMan, BlackKnight, BlackRook }
523 #if (BOARD_FILES>=10)
524 ChessSquare ShogiArray[2][BOARD_FILES] = {
525 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
526 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
527 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
528 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
531 ChessSquare XiangqiArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
533 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
535 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 ChessSquare CapablancaArray[2][BOARD_FILES] = {
539 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
541 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
542 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
545 ChessSquare GreatArray[2][BOARD_FILES] = {
546 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
547 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
548 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
549 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
552 ChessSquare JanusArray[2][BOARD_FILES] = {
553 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
554 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
555 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
556 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
560 ChessSquare GothicArray[2][BOARD_FILES] = {
561 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
562 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
563 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
564 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
567 #define GothicArray CapablancaArray
571 ChessSquare FalconArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
573 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
575 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
578 #define FalconArray CapablancaArray
581 #else // !(BOARD_FILES>=10)
582 #define XiangqiPosition FIDEArray
583 #define CapablancaArray FIDEArray
584 #define GothicArray FIDEArray
585 #define GreatArray FIDEArray
586 #endif // !(BOARD_FILES>=10)
588 #if (BOARD_FILES>=12)
589 ChessSquare CourierArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
591 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
593 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
595 #else // !(BOARD_FILES>=12)
596 #define CourierArray CapablancaArray
597 #endif // !(BOARD_FILES>=12)
600 Board initialPosition;
603 /* Convert str to a rating. Checks for special cases of "----",
605 "++++", etc. Also strips ()'s */
607 string_to_rating(str)
610 while(*str && !isdigit(*str)) ++str;
612 return 0; /* One of the special "no rating" cases */
620 /* Init programStats */
621 programStats.movelist[0] = 0;
622 programStats.depth = 0;
623 programStats.nr_moves = 0;
624 programStats.moves_left = 0;
625 programStats.nodes = 0;
626 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
627 programStats.score = 0;
628 programStats.got_only_move = 0;
629 programStats.got_fail = 0;
630 programStats.line_is_book = 0;
636 int matched, min, sec;
638 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
640 GetTimeMark(&programStartTime);
641 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
644 programStats.ok_to_send = 1;
645 programStats.seen_stat = 0;
648 * Initialize game list
654 * Internet chess server status
656 if (appData.icsActive) {
657 appData.matchMode = FALSE;
658 appData.matchGames = 0;
660 appData.noChessProgram = !appData.zippyPlay;
662 appData.zippyPlay = FALSE;
663 appData.zippyTalk = FALSE;
664 appData.noChessProgram = TRUE;
666 if (*appData.icsHelper != NULLCHAR) {
667 appData.useTelnet = TRUE;
668 appData.telnetProgram = appData.icsHelper;
671 appData.zippyTalk = appData.zippyPlay = FALSE;
674 /* [AS] Initialize pv info list [HGM] and game state */
678 for( i=0; i<=framePtr; i++ ) {
679 pvInfoList[i].depth = -1;
680 boards[i][EP_STATUS] = EP_NONE;
681 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
686 * Parse timeControl resource
688 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
689 appData.movesPerSession)) {
691 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
692 DisplayFatalError(buf, 0, 2);
696 * Parse searchTime resource
698 if (*appData.searchTime != NULLCHAR) {
699 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
701 searchTime = min * 60;
702 } else if (matched == 2) {
703 searchTime = min * 60 + sec;
706 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
707 DisplayFatalError(buf, 0, 2);
711 /* [AS] Adjudication threshold */
712 adjudicateLossThreshold = appData.adjudicateLossThreshold;
714 first.which = "first";
715 second.which = "second";
716 first.maybeThinking = second.maybeThinking = FALSE;
717 first.pr = second.pr = NoProc;
718 first.isr = second.isr = NULL;
719 first.sendTime = second.sendTime = 2;
720 first.sendDrawOffers = 1;
721 if (appData.firstPlaysBlack) {
722 first.twoMachinesColor = "black\n";
723 second.twoMachinesColor = "white\n";
725 first.twoMachinesColor = "white\n";
726 second.twoMachinesColor = "black\n";
728 first.program = appData.firstChessProgram;
729 second.program = appData.secondChessProgram;
730 first.host = appData.firstHost;
731 second.host = appData.secondHost;
732 first.dir = appData.firstDirectory;
733 second.dir = appData.secondDirectory;
734 first.other = &second;
735 second.other = &first;
736 first.initString = appData.initString;
737 second.initString = appData.secondInitString;
738 first.computerString = appData.firstComputerString;
739 second.computerString = appData.secondComputerString;
740 first.useSigint = second.useSigint = TRUE;
741 first.useSigterm = second.useSigterm = TRUE;
742 first.reuse = appData.reuseFirst;
743 second.reuse = appData.reuseSecond;
744 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
745 second.nps = appData.secondNPS;
746 first.useSetboard = second.useSetboard = FALSE;
747 first.useSAN = second.useSAN = FALSE;
748 first.usePing = second.usePing = FALSE;
749 first.lastPing = second.lastPing = 0;
750 first.lastPong = second.lastPong = 0;
751 first.usePlayother = second.usePlayother = FALSE;
752 first.useColors = second.useColors = TRUE;
753 first.useUsermove = second.useUsermove = FALSE;
754 first.sendICS = second.sendICS = FALSE;
755 first.sendName = second.sendName = appData.icsActive;
756 first.sdKludge = second.sdKludge = FALSE;
757 first.stKludge = second.stKludge = FALSE;
758 TidyProgramName(first.program, first.host, first.tidy);
759 TidyProgramName(second.program, second.host, second.tidy);
760 first.matchWins = second.matchWins = 0;
761 strcpy(first.variants, appData.variant);
762 strcpy(second.variants, appData.variant);
763 first.analysisSupport = second.analysisSupport = 2; /* detect */
764 first.analyzing = second.analyzing = FALSE;
765 first.initDone = second.initDone = FALSE;
767 /* New features added by Tord: */
768 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
769 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
770 /* End of new features added by Tord. */
771 first.fenOverride = appData.fenOverride1;
772 second.fenOverride = appData.fenOverride2;
774 /* [HGM] time odds: set factor for each machine */
775 first.timeOdds = appData.firstTimeOdds;
776 second.timeOdds = appData.secondTimeOdds;
778 if(appData.timeOddsMode) {
779 norm = first.timeOdds;
780 if(norm > second.timeOdds) norm = second.timeOdds;
782 first.timeOdds /= norm;
783 second.timeOdds /= norm;
786 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
787 first.accumulateTC = appData.firstAccumulateTC;
788 second.accumulateTC = appData.secondAccumulateTC;
789 first.maxNrOfSessions = second.maxNrOfSessions = 1;
792 first.debug = second.debug = FALSE;
793 first.supportsNPS = second.supportsNPS = UNKNOWN;
796 first.optionSettings = appData.firstOptions;
797 second.optionSettings = appData.secondOptions;
799 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
800 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
801 first.isUCI = appData.firstIsUCI; /* [AS] */
802 second.isUCI = appData.secondIsUCI; /* [AS] */
803 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
804 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
806 if (appData.firstProtocolVersion > PROTOVER ||
807 appData.firstProtocolVersion < 1) {
809 sprintf(buf, _("protocol version %d not supported"),
810 appData.firstProtocolVersion);
811 DisplayFatalError(buf, 0, 2);
813 first.protocolVersion = appData.firstProtocolVersion;
816 if (appData.secondProtocolVersion > PROTOVER ||
817 appData.secondProtocolVersion < 1) {
819 sprintf(buf, _("protocol version %d not supported"),
820 appData.secondProtocolVersion);
821 DisplayFatalError(buf, 0, 2);
823 second.protocolVersion = appData.secondProtocolVersion;
826 if (appData.icsActive) {
827 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
828 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
829 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
830 appData.clockMode = FALSE;
831 first.sendTime = second.sendTime = 0;
835 /* Override some settings from environment variables, for backward
836 compatibility. Unfortunately it's not feasible to have the env
837 vars just set defaults, at least in xboard. Ugh.
839 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
844 if (appData.noChessProgram) {
845 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
846 sprintf(programVersion, "%s", PACKAGE_STRING);
848 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
849 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
850 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
853 if (!appData.icsActive) {
855 /* Check for variants that are supported only in ICS mode,
856 or not at all. Some that are accepted here nevertheless
857 have bugs; see comments below.
859 VariantClass variant = StringToVariant(appData.variant);
861 case VariantBughouse: /* need four players and two boards */
862 case VariantKriegspiel: /* need to hide pieces and move details */
863 /* case VariantFischeRandom: (Fabien: moved below) */
864 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
865 DisplayFatalError(buf, 0, 2);
869 case VariantLoadable:
879 sprintf(buf, _("Unknown variant name %s"), appData.variant);
880 DisplayFatalError(buf, 0, 2);
883 case VariantXiangqi: /* [HGM] repetition rules not implemented */
884 case VariantFairy: /* [HGM] TestLegality definitely off! */
885 case VariantGothic: /* [HGM] should work */
886 case VariantCapablanca: /* [HGM] should work */
887 case VariantCourier: /* [HGM] initial forced moves not implemented */
888 case VariantShogi: /* [HGM] drops not tested for legality */
889 case VariantKnightmate: /* [HGM] should work */
890 case VariantCylinder: /* [HGM] untested */
891 case VariantFalcon: /* [HGM] untested */
892 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
893 offboard interposition not understood */
894 case VariantNormal: /* definitely works! */
895 case VariantWildCastle: /* pieces not automatically shuffled */
896 case VariantNoCastle: /* pieces not automatically shuffled */
897 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
898 case VariantLosers: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantSuicide: /* should work except for win condition,
901 and doesn't know captures are mandatory */
902 case VariantGiveaway: /* should work except for win condition,
903 and doesn't know captures are mandatory */
904 case VariantTwoKings: /* should work */
905 case VariantAtomic: /* should work except for win condition */
906 case Variant3Check: /* should work except for win condition */
907 case VariantShatranj: /* should work except for all win conditions */
908 case VariantMakruk: /* should work except for daw countdown */
909 case VariantBerolina: /* might work if TestLegality is off */
910 case VariantCapaRandom: /* should work */
911 case VariantJanus: /* should work */
912 case VariantSuper: /* experimental */
913 case VariantGreat: /* experimental, requires legality testing to be off */
918 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
919 InitEngineUCI( installDir, &second );
922 int NextIntegerFromString( char ** str, long * value )
927 while( *s == ' ' || *s == '\t' ) {
933 if( *s >= '0' && *s <= '9' ) {
934 while( *s >= '0' && *s <= '9' ) {
935 *value = *value * 10 + (*s - '0');
947 int NextTimeControlFromString( char ** str, long * value )
950 int result = NextIntegerFromString( str, &temp );
953 *value = temp * 60; /* Minutes */
956 result = NextIntegerFromString( str, &temp );
957 *value += temp; /* Seconds */
964 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
965 { /* [HGM] routine added to read '+moves/time' for secondary time control */
966 int result = -1; long temp, temp2;
968 if(**str != '+') return -1; // old params remain in force!
970 if( NextTimeControlFromString( str, &temp ) ) return -1;
973 /* time only: incremental or sudden-death time control */
974 if(**str == '+') { /* increment follows; read it */
976 if(result = NextIntegerFromString( str, &temp2)) return -1;
979 *moves = 0; *tc = temp * 1000;
981 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
983 (*str)++; /* classical time control */
984 result = NextTimeControlFromString( str, &temp2);
993 int GetTimeQuota(int movenr)
994 { /* [HGM] get time to add from the multi-session time-control string */
995 int moves=1; /* kludge to force reading of first session */
996 long time, increment;
997 char *s = fullTimeControlString;
999 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1001 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1002 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1003 if(movenr == -1) return time; /* last move before new session */
1004 if(!moves) return increment; /* current session is incremental */
1005 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1006 } while(movenr >= -1); /* try again for next session */
1008 return 0; // no new time quota on this move
1012 ParseTimeControl(tc, ti, mps)
1021 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1024 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1025 else sprintf(buf, "+%s+%d", tc, ti);
1028 sprintf(buf, "+%d/%s", mps, tc);
1029 else sprintf(buf, "+%s", tc);
1031 fullTimeControlString = StrSave(buf);
1033 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038 /* Parse second time control */
1041 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1049 timeControl_2 = tc2 * 1000;
1059 timeControl = tc1 * 1000;
1062 timeIncrement = ti * 1000; /* convert to ms */
1063 movesPerSession = 0;
1066 movesPerSession = mps;
1074 if (appData.debugMode) {
1075 fprintf(debugFP, "%s\n", programVersion);
1078 set_cont_sequence(appData.wrapContSeq);
1079 if (appData.matchGames > 0) {
1080 appData.matchMode = TRUE;
1081 } else if (appData.matchMode) {
1082 appData.matchGames = 1;
1084 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1085 appData.matchGames = appData.sameColorGames;
1086 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1087 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1088 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1091 if (appData.noChessProgram || first.protocolVersion == 1) {
1094 /* kludge: allow timeout for initial "feature" commands */
1096 DisplayMessage("", _("Starting chess program"));
1097 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102 InitBackEnd3 P((void))
1104 GameMode initialMode;
1108 InitChessProgram(&first, startedFromSetupPosition);
1111 if (appData.icsActive) {
1113 /* [DM] Make a console window if needed [HGM] merged ifs */
1118 if (*appData.icsCommPort != NULLCHAR) {
1119 sprintf(buf, _("Could not open comm port %s"),
1120 appData.icsCommPort);
1122 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1123 appData.icsHost, appData.icsPort);
1125 DisplayFatalError(buf, err, 1);
1130 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1132 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1133 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1134 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1135 } else if (appData.noChessProgram) {
1141 if (*appData.cmailGameName != NULLCHAR) {
1143 OpenLoopback(&cmailPR);
1145 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1149 DisplayMessage("", "");
1150 if (StrCaseCmp(appData.initialMode, "") == 0) {
1151 initialMode = BeginningOfGame;
1152 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1153 initialMode = TwoMachinesPlay;
1154 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1155 initialMode = AnalyzeFile;
1156 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1157 initialMode = AnalyzeMode;
1158 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1159 initialMode = MachinePlaysWhite;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1161 initialMode = MachinePlaysBlack;
1162 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1163 initialMode = EditGame;
1164 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1165 initialMode = EditPosition;
1166 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1167 initialMode = Training;
1169 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1170 DisplayFatalError(buf, 0, 2);
1174 if (appData.matchMode) {
1175 /* Set up machine vs. machine match */
1176 if (appData.noChessProgram) {
1177 DisplayFatalError(_("Can't have a match with no chess programs"),
1183 if (*appData.loadGameFile != NULLCHAR) {
1184 int index = appData.loadGameIndex; // [HGM] autoinc
1185 if(index<0) lastIndex = index = 1;
1186 if (!LoadGameFromFile(appData.loadGameFile,
1188 appData.loadGameFile, FALSE)) {
1189 DisplayFatalError(_("Bad game file"), 0, 1);
1192 } else if (*appData.loadPositionFile != NULLCHAR) {
1193 int index = appData.loadPositionIndex; // [HGM] autoinc
1194 if(index<0) lastIndex = index = 1;
1195 if (!LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionFile)) {
1198 DisplayFatalError(_("Bad position file"), 0, 1);
1203 } else if (*appData.cmailGameName != NULLCHAR) {
1204 /* Set up cmail mode */
1205 ReloadCmailMsgEvent(TRUE);
1207 /* Set up other modes */
1208 if (initialMode == AnalyzeFile) {
1209 if (*appData.loadGameFile == NULLCHAR) {
1210 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1214 if (*appData.loadGameFile != NULLCHAR) {
1215 (void) LoadGameFromFile(appData.loadGameFile,
1216 appData.loadGameIndex,
1217 appData.loadGameFile, TRUE);
1218 } else if (*appData.loadPositionFile != NULLCHAR) {
1219 (void) LoadPositionFromFile(appData.loadPositionFile,
1220 appData.loadPositionIndex,
1221 appData.loadPositionFile);
1222 /* [HGM] try to make self-starting even after FEN load */
1223 /* to allow automatic setup of fairy variants with wtm */
1224 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1225 gameMode = BeginningOfGame;
1226 setboardSpoiledMachineBlack = 1;
1228 /* [HGM] loadPos: make that every new game uses the setup */
1229 /* from file as long as we do not switch variant */
1230 if(!blackPlaysFirst) {
1231 startedFromPositionFile = TRUE;
1232 CopyBoard(filePosition, boards[0]);
1235 if (initialMode == AnalyzeMode) {
1236 if (appData.noChessProgram) {
1237 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1240 if (appData.icsActive) {
1241 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245 } else if (initialMode == AnalyzeFile) {
1246 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1247 ShowThinkingEvent();
1249 AnalysisPeriodicEvent(1);
1250 } else if (initialMode == MachinePlaysWhite) {
1251 if (appData.noChessProgram) {
1252 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256 if (appData.icsActive) {
1257 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261 MachineWhiteEvent();
1262 } else if (initialMode == MachinePlaysBlack) {
1263 if (appData.noChessProgram) {
1264 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268 if (appData.icsActive) {
1269 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273 MachineBlackEvent();
1274 } else if (initialMode == TwoMachinesPlay) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280 if (appData.icsActive) {
1281 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286 } else if (initialMode == EditGame) {
1288 } else if (initialMode == EditPosition) {
1289 EditPositionEvent();
1290 } else if (initialMode == Training) {
1291 if (*appData.loadGameFile == NULLCHAR) {
1292 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1301 * Establish will establish a contact to a remote host.port.
1302 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1303 * used to talk to the host.
1304 * Returns 0 if okay, error code if not.
1311 if (*appData.icsCommPort != NULLCHAR) {
1312 /* Talk to the host through a serial comm port */
1313 return OpenCommPort(appData.icsCommPort, &icsPR);
1315 } else if (*appData.gateway != NULLCHAR) {
1316 if (*appData.remoteShell == NULLCHAR) {
1317 /* Use the rcmd protocol to run telnet program on a gateway host */
1318 snprintf(buf, sizeof(buf), "%s %s %s",
1319 appData.telnetProgram, appData.icsHost, appData.icsPort);
1320 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1323 /* Use the rsh program to run telnet program on a gateway host */
1324 if (*appData.remoteUser == NULLCHAR) {
1325 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1326 appData.gateway, appData.telnetProgram,
1327 appData.icsHost, appData.icsPort);
1329 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1330 appData.remoteShell, appData.gateway,
1331 appData.remoteUser, appData.telnetProgram,
1332 appData.icsHost, appData.icsPort);
1334 return StartChildProcess(buf, "", &icsPR);
1337 } else if (appData.useTelnet) {
1338 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1341 /* TCP socket interface differs somewhat between
1342 Unix and NT; handle details in the front end.
1344 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349 show_bytes(fp, buf, count)
1355 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1356 fprintf(fp, "\\%03o", *buf & 0xff);
1365 /* Returns an errno value */
1367 OutputMaybeTelnet(pr, message, count, outError)
1373 char buf[8192], *p, *q, *buflim;
1374 int left, newcount, outcount;
1376 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1377 *appData.gateway != NULLCHAR) {
1378 if (appData.debugMode) {
1379 fprintf(debugFP, ">ICS: ");
1380 show_bytes(debugFP, message, count);
1381 fprintf(debugFP, "\n");
1383 return OutputToProcess(pr, message, count, outError);
1386 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393 if (appData.debugMode) {
1394 fprintf(debugFP, ">ICS: ");
1395 show_bytes(debugFP, buf, newcount);
1396 fprintf(debugFP, "\n");
1398 outcount = OutputToProcess(pr, buf, newcount, outError);
1399 if (outcount < newcount) return -1; /* to be sure */
1406 } else if (((unsigned char) *p) == TN_IAC) {
1407 *q++ = (char) TN_IAC;
1414 if (appData.debugMode) {
1415 fprintf(debugFP, ">ICS: ");
1416 show_bytes(debugFP, buf, newcount);
1417 fprintf(debugFP, "\n");
1419 outcount = OutputToProcess(pr, buf, newcount, outError);
1420 if (outcount < newcount) return -1; /* to be sure */
1425 read_from_player(isr, closure, message, count, error)
1432 int outError, outCount;
1433 static int gotEof = 0;
1435 /* Pass data read from player on to ICS */
1438 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1439 if (outCount < count) {
1440 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1442 } else if (count < 0) {
1443 RemoveInputSource(isr);
1444 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1445 } else if (gotEof++ > 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1453 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1454 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1455 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1456 SendToICS("date\n");
1457 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1460 /* added routine for printf style output to ics */
1461 void ics_printf(char *format, ...)
1463 char buffer[MSG_SIZ];
1466 va_start(args, format);
1467 vsnprintf(buffer, sizeof(buffer), format, args);
1468 buffer[sizeof(buffer)-1] = '\0';
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488 /* This is used for sending logon scripts to the ICS. Sending
1489 without a delay causes problems when using timestamp on ICC
1490 (at least on my machine). */
1492 SendToICSDelayed(s,msdelay)
1496 int count, outCount, outError;
1498 if (icsPR == NULL) return;
1501 if (appData.debugMode) {
1502 fprintf(debugFP, ">ICS: ");
1503 show_bytes(debugFP, s, count);
1504 fprintf(debugFP, "\n");
1506 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1508 if (outCount < count) {
1509 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1514 /* Remove all highlighting escape sequences in s
1515 Also deletes any suffix starting with '('
1518 StripHighlightAndTitle(s)
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1530 if (*s == '(' || *s == '[') {
1541 /* Remove all highlighting escape sequences in s */
1546 static char retbuf[MSG_SIZ];
1549 while (*s != NULLCHAR) {
1550 while (*s == '\033') {
1551 while (*s != NULLCHAR && !isalpha(*s)) s++;
1552 if (*s != NULLCHAR) s++;
1554 while (*s != NULLCHAR && *s != '\033') {
1562 char *variantNames[] = VARIANT_NAMES;
1567 return variantNames[v];
1571 /* Identify a variant from the strings the chess servers use or the
1572 PGN Variant tag names we use. */
1579 VariantClass v = VariantNormal;
1580 int i, found = FALSE;
1585 /* [HGM] skip over optional board-size prefixes */
1586 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1587 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1588 while( *e++ != '_');
1591 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1595 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1596 if (StrCaseStr(e, variantNames[i])) {
1597 v = (VariantClass) i;
1604 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1605 || StrCaseStr(e, "wild/fr")
1606 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1607 v = VariantFischeRandom;
1608 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1609 (i = 1, p = StrCaseStr(e, "w"))) {
1611 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1618 case 0: /* FICS only, actually */
1620 /* Castling legal even if K starts on d-file */
1621 v = VariantWildCastle;
1626 /* Castling illegal even if K & R happen to start in
1627 normal positions. */
1628 v = VariantNoCastle;
1641 /* Castling legal iff K & R start in normal positions */
1647 /* Special wilds for position setup; unclear what to do here */
1648 v = VariantLoadable;
1651 /* Bizarre ICC game */
1652 v = VariantTwoKings;
1655 v = VariantKriegspiel;
1661 v = VariantFischeRandom;
1664 v = VariantCrazyhouse;
1667 v = VariantBughouse;
1673 /* Not quite the same as FICS suicide! */
1674 v = VariantGiveaway;
1680 v = VariantShatranj;
1683 /* Temporary names for future ICC types. The name *will* change in
1684 the next xboard/WinBoard release after ICC defines it. */
1722 v = VariantCapablanca;
1725 v = VariantKnightmate;
1731 v = VariantCylinder;
1737 v = VariantCapaRandom;
1740 v = VariantBerolina;
1752 /* Found "wild" or "w" in the string but no number;
1753 must assume it's normal chess. */
1757 sprintf(buf, _("Unknown wild type %d"), wnum);
1758 DisplayError(buf, 0);
1764 if (appData.debugMode) {
1765 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1766 e, wnum, VariantName(v));
1771 static int leftover_start = 0, leftover_len = 0;
1772 char star_match[STAR_MATCH_N][MSG_SIZ];
1774 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1775 advance *index beyond it, and set leftover_start to the new value of
1776 *index; else return FALSE. If pattern contains the character '*', it
1777 matches any sequence of characters not containing '\r', '\n', or the
1778 character following the '*' (if any), and the matched sequence(s) are
1779 copied into star_match.
1782 looking_at(buf, index, pattern)
1787 char *bufp = &buf[*index], *patternp = pattern;
1789 char *matchp = star_match[0];
1792 if (*patternp == NULLCHAR) {
1793 *index = leftover_start = bufp - buf;
1797 if (*bufp == NULLCHAR) return FALSE;
1798 if (*patternp == '*') {
1799 if (*bufp == *(patternp + 1)) {
1801 matchp = star_match[++star_count];
1805 } else if (*bufp == '\n' || *bufp == '\r') {
1807 if (*patternp == NULLCHAR)
1812 *matchp++ = *bufp++;
1816 if (*patternp != *bufp) return FALSE;
1823 SendToPlayer(data, length)
1827 int error, outCount;
1828 outCount = OutputToProcess(NoProc, data, length, &error);
1829 if (outCount < length) {
1830 DisplayFatalError(_("Error writing to display"), error, 1);
1835 PackHolding(packed, holding)
1847 switch (runlength) {
1858 sprintf(q, "%d", runlength);
1870 /* Telnet protocol requests from the front end */
1872 TelnetRequest(ddww, option)
1873 unsigned char ddww, option;
1875 unsigned char msg[3];
1876 int outCount, outError;
1878 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1880 if (appData.debugMode) {
1881 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1897 sprintf(buf1, "%d", ddww);
1906 sprintf(buf2, "%d", option);
1909 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1914 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1916 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923 if (!appData.icsActive) return;
1924 TelnetRequest(TN_DO, TN_ECHO);
1930 if (!appData.icsActive) return;
1931 TelnetRequest(TN_DONT, TN_ECHO);
1935 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1937 /* put the holdings sent to us by the server on the board holdings area */
1938 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1942 if(gameInfo.holdingsWidth < 2) return;
1943 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1944 return; // prevent overwriting by pre-board holdings
1946 if( (int)lowestPiece >= BlackPawn ) {
1949 holdingsStartRow = BOARD_HEIGHT-1;
1952 holdingsColumn = BOARD_WIDTH-1;
1953 countsColumn = BOARD_WIDTH-2;
1954 holdingsStartRow = 0;
1958 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1959 board[i][holdingsColumn] = EmptySquare;
1960 board[i][countsColumn] = (ChessSquare) 0;
1962 while( (p=*holdings++) != NULLCHAR ) {
1963 piece = CharToPiece( ToUpper(p) );
1964 if(piece == EmptySquare) continue;
1965 /*j = (int) piece - (int) WhitePawn;*/
1966 j = PieceToNumber(piece);
1967 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1968 if(j < 0) continue; /* should not happen */
1969 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1970 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1971 board[holdingsStartRow+j*direction][countsColumn]++;
1977 VariantSwitch(Board board, VariantClass newVariant)
1979 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1982 startedFromPositionFile = FALSE;
1983 if(gameInfo.variant == newVariant) return;
1985 /* [HGM] This routine is called each time an assignment is made to
1986 * gameInfo.variant during a game, to make sure the board sizes
1987 * are set to match the new variant. If that means adding or deleting
1988 * holdings, we shift the playing board accordingly
1989 * This kludge is needed because in ICS observe mode, we get boards
1990 * of an ongoing game without knowing the variant, and learn about the
1991 * latter only later. This can be because of the move list we requested,
1992 * in which case the game history is refilled from the beginning anyway,
1993 * but also when receiving holdings of a crazyhouse game. In the latter
1994 * case we want to add those holdings to the already received position.
1998 if (appData.debugMode) {
1999 fprintf(debugFP, "Switch board from %s to %s\n",
2000 VariantName(gameInfo.variant), VariantName(newVariant));
2001 setbuf(debugFP, NULL);
2003 shuffleOpenings = 0; /* [HGM] shuffle */
2004 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2008 newWidth = 9; newHeight = 9;
2009 gameInfo.holdingsSize = 7;
2010 case VariantBughouse:
2011 case VariantCrazyhouse:
2012 newHoldingsWidth = 2; break;
2016 newHoldingsWidth = 2;
2017 gameInfo.holdingsSize = 8;
2020 case VariantCapablanca:
2021 case VariantCapaRandom:
2024 newHoldingsWidth = gameInfo.holdingsSize = 0;
2027 if(newWidth != gameInfo.boardWidth ||
2028 newHeight != gameInfo.boardHeight ||
2029 newHoldingsWidth != gameInfo.holdingsWidth ) {
2031 /* shift position to new playing area, if needed */
2032 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2033 for(i=0; i<BOARD_HEIGHT; i++)
2034 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2035 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2037 for(i=0; i<newHeight; i++) {
2038 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2039 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2041 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2042 for(i=0; i<BOARD_HEIGHT; i++)
2043 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2044 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2047 gameInfo.boardWidth = newWidth;
2048 gameInfo.boardHeight = newHeight;
2049 gameInfo.holdingsWidth = newHoldingsWidth;
2050 gameInfo.variant = newVariant;
2051 InitDrawingSizes(-2, 0);
2052 } else gameInfo.variant = newVariant;
2053 CopyBoard(oldBoard, board); // remember correctly formatted board
2054 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2055 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2058 static int loggedOn = FALSE;
2060 /*-- Game start info cache: --*/
2062 char gs_kind[MSG_SIZ];
2063 static char player1Name[128] = "";
2064 static char player2Name[128] = "";
2065 static char cont_seq[] = "\n\\ ";
2066 static int player1Rating = -1;
2067 static int player2Rating = -1;
2068 /*----------------------------*/
2070 ColorClass curColor = ColorNormal;
2071 int suppressKibitz = 0;
2074 Boolean soughtPending = FALSE;
2075 Boolean seekGraphUp;
2076 #define MAX_SEEK_ADS 200
2078 char *seekAdList[MAX_SEEK_ADS];
2079 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2080 float tcList[MAX_SEEK_ADS];
2081 char colorList[MAX_SEEK_ADS];
2082 int nrOfSeekAds = 0;
2083 int minRating = 1010, maxRating = 2800;
2084 int hMargin = 10, vMargin = 20, h, w;
2085 extern int squareSize, lineGap;
2090 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2091 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2092 if(r < minRating+100 && r >=0 ) r = minRating+100;
2093 if(r > maxRating) r = maxRating;
2094 if(tc < 1.) tc = 1.;
2095 if(tc > 95.) tc = 95.;
2096 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2097 y = ((double)r - minRating)/(maxRating - minRating)
2098 * (h-vMargin-squareSize/8-1) + vMargin;
2099 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2100 if(strstr(seekAdList[i], " u ")) color = 1;
2101 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2102 !strstr(seekAdList[i], "bullet") &&
2103 !strstr(seekAdList[i], "blitz") &&
2104 !strstr(seekAdList[i], "standard") ) color = 2;
2105 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2106 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2110 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2112 char buf[MSG_SIZ], *ext = "";
2113 VariantClass v = StringToVariant(type);
2114 if(strstr(type, "wild")) {
2115 ext = type + 4; // append wild number
2116 if(v == VariantFischeRandom) type = "chess960"; else
2117 if(v == VariantLoadable) type = "setup"; else
2118 type = VariantName(v);
2120 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2121 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2122 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2123 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2124 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2125 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2126 seekNrList[nrOfSeekAds] = nr;
2127 zList[nrOfSeekAds] = 0;
2128 seekAdList[nrOfSeekAds++] = StrSave(buf);
2129 if(plot) PlotSeekAd(nrOfSeekAds-1);
2136 int x = xList[i], y = yList[i], d=squareSize/4, k;
2137 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2138 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2139 // now replot every dot that overlapped
2140 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2141 int xx = xList[k], yy = yList[k];
2142 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2143 DrawSeekDot(xx, yy, colorList[k]);
2148 RemoveSeekAd(int nr)
2151 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2153 if(seekAdList[i]) free(seekAdList[i]);
2154 seekAdList[i] = seekAdList[--nrOfSeekAds];
2155 seekNrList[i] = seekNrList[nrOfSeekAds];
2156 ratingList[i] = ratingList[nrOfSeekAds];
2157 colorList[i] = colorList[nrOfSeekAds];
2158 tcList[i] = tcList[nrOfSeekAds];
2159 xList[i] = xList[nrOfSeekAds];
2160 yList[i] = yList[nrOfSeekAds];
2161 zList[i] = zList[nrOfSeekAds];
2162 seekAdList[nrOfSeekAds] = NULL;
2168 MatchSoughtLine(char *line)
2170 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2171 int nr, base, inc, u=0; char dummy;
2173 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2174 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2176 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2177 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2178 // match: compact and save the line
2179 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2188 if(!seekGraphUp) return FALSE;
2190 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2191 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2193 DrawSeekBackground(0, 0, w, h);
2194 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2195 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2196 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2197 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2199 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2202 sprintf(buf, "%d", i);
2203 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2206 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2207 for(i=1; i<100; i+=(i<10?1:5)) {
2208 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2209 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2210 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2212 sprintf(buf, "%d", i);
2213 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2216 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2220 int SeekGraphClick(ClickType click, int x, int y, int moving)
2222 static int lastDown = 0, displayed = 0, lastSecond;
2223 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2224 if(click == Release || moving) return FALSE;
2226 soughtPending = TRUE;
2227 SendToICS(ics_prefix);
2228 SendToICS("sought\n"); // should this be "sought all"?
2229 } else { // issue challenge based on clicked ad
2230 int dist = 10000; int i, closest = 0, second = 0;
2231 for(i=0; i<nrOfSeekAds; i++) {
2232 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2233 if(d < dist) { dist = d; closest = i; }
2234 second += (d - zList[i] < 120); // count in-range ads
2235 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2239 second = (second > 1);
2240 if(displayed != closest || second != lastSecond) {
2241 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2242 lastSecond = second; displayed = closest;
2244 if(click == Press) {
2245 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2248 } // on press 'hit', only show info
2249 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2250 sprintf(buf, "play %d\n", seekNrList[closest]);
2251 SendToICS(ics_prefix);
2253 return TRUE; // let incoming board of started game pop down the graph
2254 } else if(click == Release) { // release 'miss' is ignored
2255 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2256 if(moving == 2) { // right up-click
2257 nrOfSeekAds = 0; // refresh graph
2258 soughtPending = TRUE;
2259 SendToICS(ics_prefix);
2260 SendToICS("sought\n"); // should this be "sought all"?
2263 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2264 // press miss or release hit 'pop down' seek graph
2265 seekGraphUp = FALSE;
2266 DrawPosition(TRUE, NULL);
2272 read_from_ics(isr, closure, data, count, error)
2279 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2280 #define STARTED_NONE 0
2281 #define STARTED_MOVES 1
2282 #define STARTED_BOARD 2
2283 #define STARTED_OBSERVE 3
2284 #define STARTED_HOLDINGS 4
2285 #define STARTED_CHATTER 5
2286 #define STARTED_COMMENT 6
2287 #define STARTED_MOVES_NOHIDE 7
2289 static int started = STARTED_NONE;
2290 static char parse[20000];
2291 static int parse_pos = 0;
2292 static char buf[BUF_SIZE + 1];
2293 static int firstTime = TRUE, intfSet = FALSE;
2294 static ColorClass prevColor = ColorNormal;
2295 static int savingComment = FALSE;
2296 static int cmatch = 0; // continuation sequence match
2303 int backup; /* [DM] For zippy color lines */
2305 char talker[MSG_SIZ]; // [HGM] chat
2308 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2310 if (appData.debugMode) {
2312 fprintf(debugFP, "<ICS: ");
2313 show_bytes(debugFP, data, count);
2314 fprintf(debugFP, "\n");
2318 if (appData.debugMode) { int f = forwardMostMove;
2319 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2320 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2321 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2324 /* If last read ended with a partial line that we couldn't parse,
2325 prepend it to the new read and try again. */
2326 if (leftover_len > 0) {
2327 for (i=0; i<leftover_len; i++)
2328 buf[i] = buf[leftover_start + i];
2331 /* copy new characters into the buffer */
2332 bp = buf + leftover_len;
2333 buf_len=leftover_len;
2334 for (i=0; i<count; i++)
2337 if (data[i] == '\r')
2340 // join lines split by ICS?
2341 if (!appData.noJoin)
2344 Joining just consists of finding matches against the
2345 continuation sequence, and discarding that sequence
2346 if found instead of copying it. So, until a match
2347 fails, there's nothing to do since it might be the
2348 complete sequence, and thus, something we don't want
2351 if (data[i] == cont_seq[cmatch])
2354 if (cmatch == strlen(cont_seq))
2356 cmatch = 0; // complete match. just reset the counter
2359 it's possible for the ICS to not include the space
2360 at the end of the last word, making our [correct]
2361 join operation fuse two separate words. the server
2362 does this when the space occurs at the width setting.
2364 if (!buf_len || buf[buf_len-1] != ' ')
2375 match failed, so we have to copy what matched before
2376 falling through and copying this character. In reality,
2377 this will only ever be just the newline character, but
2378 it doesn't hurt to be precise.
2380 strncpy(bp, cont_seq, cmatch);
2392 buf[buf_len] = NULLCHAR;
2393 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2398 while (i < buf_len) {
2399 /* Deal with part of the TELNET option negotiation
2400 protocol. We refuse to do anything beyond the
2401 defaults, except that we allow the WILL ECHO option,
2402 which ICS uses to turn off password echoing when we are
2403 directly connected to it. We reject this option
2404 if localLineEditing mode is on (always on in xboard)
2405 and we are talking to port 23, which might be a real
2406 telnet server that will try to keep WILL ECHO on permanently.
2408 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2409 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2410 unsigned char option;
2412 switch ((unsigned char) buf[++i]) {
2414 if (appData.debugMode)
2415 fprintf(debugFP, "\n<WILL ");
2416 switch (option = (unsigned char) buf[++i]) {
2418 if (appData.debugMode)
2419 fprintf(debugFP, "ECHO ");
2420 /* Reply only if this is a change, according
2421 to the protocol rules. */
2422 if (remoteEchoOption) break;
2423 if (appData.localLineEditing &&
2424 atoi(appData.icsPort) == TN_PORT) {
2425 TelnetRequest(TN_DONT, TN_ECHO);
2428 TelnetRequest(TN_DO, TN_ECHO);
2429 remoteEchoOption = TRUE;
2433 if (appData.debugMode)
2434 fprintf(debugFP, "%d ", option);
2435 /* Whatever this is, we don't want it. */
2436 TelnetRequest(TN_DONT, option);
2441 if (appData.debugMode)
2442 fprintf(debugFP, "\n<WONT ");
2443 switch (option = (unsigned char) buf[++i]) {
2445 if (appData.debugMode)
2446 fprintf(debugFP, "ECHO ");
2447 /* Reply only if this is a change, according
2448 to the protocol rules. */
2449 if (!remoteEchoOption) break;
2451 TelnetRequest(TN_DONT, TN_ECHO);
2452 remoteEchoOption = FALSE;
2455 if (appData.debugMode)
2456 fprintf(debugFP, "%d ", (unsigned char) option);
2457 /* Whatever this is, it must already be turned
2458 off, because we never agree to turn on
2459 anything non-default, so according to the
2460 protocol rules, we don't reply. */
2465 if (appData.debugMode)
2466 fprintf(debugFP, "\n<DO ");
2467 switch (option = (unsigned char) buf[++i]) {
2469 /* Whatever this is, we refuse to do it. */
2470 if (appData.debugMode)
2471 fprintf(debugFP, "%d ", option);
2472 TelnetRequest(TN_WONT, option);
2477 if (appData.debugMode)
2478 fprintf(debugFP, "\n<DONT ");
2479 switch (option = (unsigned char) buf[++i]) {
2481 if (appData.debugMode)
2482 fprintf(debugFP, "%d ", option);
2483 /* Whatever this is, we are already not doing
2484 it, because we never agree to do anything
2485 non-default, so according to the protocol
2486 rules, we don't reply. */
2491 if (appData.debugMode)
2492 fprintf(debugFP, "\n<IAC ");
2493 /* Doubled IAC; pass it through */
2497 if (appData.debugMode)
2498 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2499 /* Drop all other telnet commands on the floor */
2502 if (oldi > next_out)
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2509 /* OK, this at least will *usually* work */
2510 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2514 if (loggedOn && !intfSet) {
2515 if (ics_type == ICS_ICC) {
2517 "/set-quietly interface %s\n/set-quietly style 12\n",
2519 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2520 strcat(str, "/set-2 51 1\n/set seek 1\n");
2521 } else if (ics_type == ICS_CHESSNET) {
2522 sprintf(str, "/style 12\n");
2524 strcpy(str, "alias $ @\n$set interface ");
2525 strcat(str, programVersion);
2526 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2527 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2528 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2530 strcat(str, "$iset nohighlight 1\n");
2532 strcat(str, "$iset lock 1\n$style 12\n");
2535 NotifyFrontendLogin();
2539 if (started == STARTED_COMMENT) {
2540 /* Accumulate characters in comment */
2541 parse[parse_pos++] = buf[i];
2542 if (buf[i] == '\n') {
2543 parse[parse_pos] = NULLCHAR;
2544 if(chattingPartner>=0) {
2546 sprintf(mess, "%s%s", talker, parse);
2547 OutputChatMessage(chattingPartner, mess);
2548 chattingPartner = -1;
2549 next_out = i+1; // [HGM] suppress printing in ICS window
2551 if(!suppressKibitz) // [HGM] kibitz
2552 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2553 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2554 int nrDigit = 0, nrAlph = 0, j;
2555 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2556 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2557 parse[parse_pos] = NULLCHAR;
2558 // try to be smart: if it does not look like search info, it should go to
2559 // ICS interaction window after all, not to engine-output window.
2560 for(j=0; j<parse_pos; j++) { // count letters and digits
2561 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2562 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2563 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2565 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2566 int depth=0; float score;
2567 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2568 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2569 pvInfoList[forwardMostMove-1].depth = depth;
2570 pvInfoList[forwardMostMove-1].score = 100*score;
2572 OutputKibitz(suppressKibitz, parse);
2575 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2576 SendToPlayer(tmp, strlen(tmp));
2578 next_out = i+1; // [HGM] suppress printing in ICS window
2580 started = STARTED_NONE;
2582 /* Don't match patterns against characters in comment */
2587 if (started == STARTED_CHATTER) {
2588 if (buf[i] != '\n') {
2589 /* Don't match patterns against characters in chatter */
2593 started = STARTED_NONE;
2594 if(suppressKibitz) next_out = i+1;
2597 /* Kludge to deal with rcmd protocol */
2598 if (firstTime && looking_at(buf, &i, "\001*")) {
2599 DisplayFatalError(&buf[1], 0, 1);
2605 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2608 if (appData.debugMode)
2609 fprintf(debugFP, "ics_type %d\n", ics_type);
2612 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2613 ics_type = ICS_FICS;
2615 if (appData.debugMode)
2616 fprintf(debugFP, "ics_type %d\n", ics_type);
2619 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2620 ics_type = ICS_CHESSNET;
2622 if (appData.debugMode)
2623 fprintf(debugFP, "ics_type %d\n", ics_type);
2628 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2629 looking_at(buf, &i, "Logging you in as \"*\"") ||
2630 looking_at(buf, &i, "will be \"*\""))) {
2631 strcpy(ics_handle, star_match[0]);
2635 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2637 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2638 DisplayIcsInteractionTitle(buf);
2639 have_set_title = TRUE;
2642 /* skip finger notes */
2643 if (started == STARTED_NONE &&
2644 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2645 (buf[i] == '1' && buf[i+1] == '0')) &&
2646 buf[i+2] == ':' && buf[i+3] == ' ') {
2647 started = STARTED_CHATTER;
2653 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2654 if(appData.seekGraph) {
2655 if(soughtPending && MatchSoughtLine(buf+i)) {
2656 i = strstr(buf+i, "rated") - buf;
2657 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2658 next_out = leftover_start = i;
2659 started = STARTED_CHATTER;
2660 suppressKibitz = TRUE;
2663 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2664 && looking_at(buf, &i, "* ads displayed")) {
2665 soughtPending = FALSE;
2670 if(appData.autoRefresh) {
2671 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2672 int s = (ics_type == ICS_ICC); // ICC format differs
2674 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2675 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2676 looking_at(buf, &i, "*% "); // eat prompt
2677 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2678 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679 next_out = i; // suppress
2682 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2683 char *p = star_match[0];
2685 if(seekGraphUp) RemoveSeekAd(atoi(p));
2686 while(*p && *p++ != ' '); // next
2688 looking_at(buf, &i, "*% "); // eat prompt
2689 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2696 /* skip formula vars */
2697 if (started == STARTED_NONE &&
2698 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2699 started = STARTED_CHATTER;
2704 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2705 if (appData.autoKibitz && started == STARTED_NONE &&
2706 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2707 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2708 if(looking_at(buf, &i, "* kibitzes: ") &&
2709 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2710 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2711 suppressKibitz = TRUE;
2712 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2714 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2715 && (gameMode == IcsPlayingWhite)) ||
2716 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2717 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2718 started = STARTED_CHATTER; // own kibitz we simply discard
2720 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2721 parse_pos = 0; parse[0] = NULLCHAR;
2722 savingComment = TRUE;
2723 suppressKibitz = gameMode != IcsObserving ? 2 :
2724 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2728 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2729 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2730 && atoi(star_match[0])) {
2731 // suppress the acknowledgements of our own autoKibitz
2733 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2734 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2735 SendToPlayer(star_match[0], strlen(star_match[0]));
2736 if(looking_at(buf, &i, "*% ")) // eat prompt
2737 suppressKibitz = FALSE;
2741 } // [HGM] kibitz: end of patch
2743 // [HGM] chat: intercept tells by users for which we have an open chat window
2745 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2746 looking_at(buf, &i, "* whispers:") ||
2747 looking_at(buf, &i, "* kibitzes:") ||
2748 looking_at(buf, &i, "* shouts:") ||
2749 looking_at(buf, &i, "* c-shouts:") ||
2750 looking_at(buf, &i, "--> * ") ||
2751 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2752 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2753 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2754 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2756 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2757 chattingPartner = -1;
2759 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2760 for(p=0; p<MAX_CHAT; p++) {
2761 if(channel == atoi(chatPartner[p])) {
2762 talker[0] = '['; strcat(talker, "] ");
2763 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2764 chattingPartner = p; break;
2767 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2768 for(p=0; p<MAX_CHAT; p++) {
2769 if(!strcmp("kibitzes", chatPartner[p])) {
2770 talker[0] = '['; strcat(talker, "] ");
2771 chattingPartner = p; break;
2774 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2775 for(p=0; p<MAX_CHAT; p++) {
2776 if(!strcmp("whispers", chatPartner[p])) {
2777 talker[0] = '['; strcat(talker, "] ");
2778 chattingPartner = p; break;
2781 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2782 if(buf[i-8] == '-' && buf[i-3] == 't')
2783 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2784 if(!strcmp("c-shouts", chatPartner[p])) {
2785 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2786 chattingPartner = p; break;
2789 if(chattingPartner < 0)
2790 for(p=0; p<MAX_CHAT; p++) {
2791 if(!strcmp("shouts", chatPartner[p])) {
2792 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2793 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2794 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2795 chattingPartner = p; break;
2799 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2800 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2801 talker[0] = 0; Colorize(ColorTell, FALSE);
2802 chattingPartner = p; break;
2804 if(chattingPartner<0) i = oldi; else {
2805 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2806 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2807 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2808 started = STARTED_COMMENT;
2809 parse_pos = 0; parse[0] = NULLCHAR;
2810 savingComment = 3 + chattingPartner; // counts as TRUE
2811 suppressKibitz = TRUE;
2814 } // [HGM] chat: end of patch
2816 if (appData.zippyTalk || appData.zippyPlay) {
2817 /* [DM] Backup address for color zippy lines */
2821 if (loggedOn == TRUE)
2822 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2823 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2825 if (ZippyControl(buf, &i) ||
2826 ZippyConverse(buf, &i) ||
2827 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2829 if (!appData.colorize) continue;
2833 } // [DM] 'else { ' deleted
2835 /* Regular tells and says */
2836 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2837 looking_at(buf, &i, "* (your partner) tells you: ") ||
2838 looking_at(buf, &i, "* says: ") ||
2839 /* Don't color "message" or "messages" output */
2840 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2841 looking_at(buf, &i, "*. * at *:*: ") ||
2842 looking_at(buf, &i, "--* (*:*): ") ||
2843 /* Message notifications (same color as tells) */
2844 looking_at(buf, &i, "* has left a message ") ||
2845 looking_at(buf, &i, "* just sent you a message:\n") ||
2846 /* Whispers and kibitzes */
2847 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2848 looking_at(buf, &i, "* kibitzes: ") ||
2850 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2852 if (tkind == 1 && strchr(star_match[0], ':')) {
2853 /* Avoid "tells you:" spoofs in channels */
2856 if (star_match[0][0] == NULLCHAR ||
2857 strchr(star_match[0], ' ') ||
2858 (tkind == 3 && strchr(star_match[1], ' '))) {
2859 /* Reject bogus matches */
2862 if (appData.colorize) {
2863 if (oldi > next_out) {
2864 SendToPlayer(&buf[next_out], oldi - next_out);
2869 Colorize(ColorTell, FALSE);
2870 curColor = ColorTell;
2873 Colorize(ColorKibitz, FALSE);
2874 curColor = ColorKibitz;
2877 p = strrchr(star_match[1], '(');
2884 Colorize(ColorChannel1, FALSE);
2885 curColor = ColorChannel1;
2887 Colorize(ColorChannel, FALSE);
2888 curColor = ColorChannel;
2892 curColor = ColorNormal;
2896 if (started == STARTED_NONE && appData.autoComment &&
2897 (gameMode == IcsObserving ||
2898 gameMode == IcsPlayingWhite ||
2899 gameMode == IcsPlayingBlack)) {
2900 parse_pos = i - oldi;
2901 memcpy(parse, &buf[oldi], parse_pos);
2902 parse[parse_pos] = NULLCHAR;
2903 started = STARTED_COMMENT;
2904 savingComment = TRUE;
2906 started = STARTED_CHATTER;
2907 savingComment = FALSE;
2914 if (looking_at(buf, &i, "* s-shouts: ") ||
2915 looking_at(buf, &i, "* c-shouts: ")) {
2916 if (appData.colorize) {
2917 if (oldi > next_out) {
2918 SendToPlayer(&buf[next_out], oldi - next_out);
2921 Colorize(ColorSShout, FALSE);
2922 curColor = ColorSShout;
2925 started = STARTED_CHATTER;
2929 if (looking_at(buf, &i, "--->")) {
2934 if (looking_at(buf, &i, "* shouts: ") ||
2935 looking_at(buf, &i, "--> ")) {
2936 if (appData.colorize) {
2937 if (oldi > next_out) {
2938 SendToPlayer(&buf[next_out], oldi - next_out);
2941 Colorize(ColorShout, FALSE);
2942 curColor = ColorShout;
2945 started = STARTED_CHATTER;
2949 if (looking_at( buf, &i, "Challenge:")) {
2950 if (appData.colorize) {
2951 if (oldi > next_out) {
2952 SendToPlayer(&buf[next_out], oldi - next_out);
2955 Colorize(ColorChallenge, FALSE);
2956 curColor = ColorChallenge;
2962 if (looking_at(buf, &i, "* offers you") ||
2963 looking_at(buf, &i, "* offers to be") ||
2964 looking_at(buf, &i, "* would like to") ||
2965 looking_at(buf, &i, "* requests to") ||
2966 looking_at(buf, &i, "Your opponent offers") ||
2967 looking_at(buf, &i, "Your opponent requests")) {
2969 if (appData.colorize) {
2970 if (oldi > next_out) {
2971 SendToPlayer(&buf[next_out], oldi - next_out);
2974 Colorize(ColorRequest, FALSE);
2975 curColor = ColorRequest;
2980 if (looking_at(buf, &i, "* (*) seeking")) {
2981 if (appData.colorize) {
2982 if (oldi > next_out) {
2983 SendToPlayer(&buf[next_out], oldi - next_out);
2986 Colorize(ColorSeek, FALSE);
2987 curColor = ColorSeek;
2992 if (looking_at(buf, &i, "\\ ")) {
2993 if (prevColor != ColorNormal) {
2994 if (oldi > next_out) {
2995 SendToPlayer(&buf[next_out], oldi - next_out);
2998 Colorize(prevColor, TRUE);
2999 curColor = prevColor;
3001 if (savingComment) {
3002 parse_pos = i - oldi;
3003 memcpy(parse, &buf[oldi], parse_pos);
3004 parse[parse_pos] = NULLCHAR;
3005 started = STARTED_COMMENT;
3006 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3007 chattingPartner = savingComment - 3; // kludge to remember the box
3009 started = STARTED_CHATTER;
3014 if (looking_at(buf, &i, "Black Strength :") ||
3015 looking_at(buf, &i, "<<< style 10 board >>>") ||
3016 looking_at(buf, &i, "<10>") ||
3017 looking_at(buf, &i, "#@#")) {
3018 /* Wrong board style */
3020 SendToICS(ics_prefix);
3021 SendToICS("set style 12\n");
3022 SendToICS(ics_prefix);
3023 SendToICS("refresh\n");
3027 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3029 have_sent_ICS_logon = 1;
3033 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3034 (looking_at(buf, &i, "\n<12> ") ||
3035 looking_at(buf, &i, "<12> "))) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 started = STARTED_BOARD;
3046 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3047 looking_at(buf, &i, "<b1> ")) {
3048 if (oldi > next_out) {
3049 SendToPlayer(&buf[next_out], oldi - next_out);
3052 started = STARTED_HOLDINGS;
3057 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3059 /* Header for a move list -- first line */
3061 switch (ics_getting_history) {
3065 case BeginningOfGame:
3066 /* User typed "moves" or "oldmoves" while we
3067 were idle. Pretend we asked for these
3068 moves and soak them up so user can step
3069 through them and/or save them.
3072 gameMode = IcsObserving;
3075 ics_getting_history = H_GOT_UNREQ_HEADER;
3077 case EditGame: /*?*/
3078 case EditPosition: /*?*/
3079 /* Should above feature work in these modes too? */
3080 /* For now it doesn't */
3081 ics_getting_history = H_GOT_UNWANTED_HEADER;
3084 ics_getting_history = H_GOT_UNWANTED_HEADER;
3089 /* Is this the right one? */
3090 if (gameInfo.white && gameInfo.black &&
3091 strcmp(gameInfo.white, star_match[0]) == 0 &&
3092 strcmp(gameInfo.black, star_match[2]) == 0) {
3094 ics_getting_history = H_GOT_REQ_HEADER;
3097 case H_GOT_REQ_HEADER:
3098 case H_GOT_UNREQ_HEADER:
3099 case H_GOT_UNWANTED_HEADER:
3100 case H_GETTING_MOVES:
3101 /* Should not happen */
3102 DisplayError(_("Error gathering move list: two headers"), 0);
3103 ics_getting_history = H_FALSE;
3107 /* Save player ratings into gameInfo if needed */
3108 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3109 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3110 (gameInfo.whiteRating == -1 ||
3111 gameInfo.blackRating == -1)) {
3113 gameInfo.whiteRating = string_to_rating(star_match[1]);
3114 gameInfo.blackRating = string_to_rating(star_match[3]);
3115 if (appData.debugMode)
3116 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3117 gameInfo.whiteRating, gameInfo.blackRating);
3122 if (looking_at(buf, &i,
3123 "* * match, initial time: * minute*, increment: * second")) {
3124 /* Header for a move list -- second line */
3125 /* Initial board will follow if this is a wild game */
3126 if (gameInfo.event != NULL) free(gameInfo.event);
3127 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3128 gameInfo.event = StrSave(str);
3129 /* [HGM] we switched variant. Translate boards if needed. */
3130 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3134 if (looking_at(buf, &i, "Move ")) {
3135 /* Beginning of a move list */
3136 switch (ics_getting_history) {
3138 /* Normally should not happen */
3139 /* Maybe user hit reset while we were parsing */
3142 /* Happens if we are ignoring a move list that is not
3143 * the one we just requested. Common if the user
3144 * tries to observe two games without turning off
3147 case H_GETTING_MOVES:
3148 /* Should not happen */
3149 DisplayError(_("Error gathering move list: nested"), 0);
3150 ics_getting_history = H_FALSE;
3152 case H_GOT_REQ_HEADER:
3153 ics_getting_history = H_GETTING_MOVES;
3154 started = STARTED_MOVES;
3156 if (oldi > next_out) {
3157 SendToPlayer(&buf[next_out], oldi - next_out);
3160 case H_GOT_UNREQ_HEADER:
3161 ics_getting_history = H_GETTING_MOVES;
3162 started = STARTED_MOVES_NOHIDE;
3165 case H_GOT_UNWANTED_HEADER:
3166 ics_getting_history = H_FALSE;
3172 if (looking_at(buf, &i, "% ") ||
3173 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3174 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3175 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3176 soughtPending = FALSE;
3180 if(suppressKibitz) next_out = i;
3181 savingComment = FALSE;
3185 case STARTED_MOVES_NOHIDE:
3186 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3187 parse[parse_pos + i - oldi] = NULLCHAR;
3188 ParseGameHistory(parse);
3190 if (appData.zippyPlay && first.initDone) {
3191 FeedMovesToProgram(&first, forwardMostMove);
3192 if (gameMode == IcsPlayingWhite) {
3193 if (WhiteOnMove(forwardMostMove)) {
3194 if (first.sendTime) {
3195 if (first.useColors) {
3196 SendToProgram("black\n", &first);
3198 SendTimeRemaining(&first, TRUE);
3200 if (first.useColors) {
3201 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3203 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3204 first.maybeThinking = TRUE;
3206 if (first.usePlayother) {
3207 if (first.sendTime) {
3208 SendTimeRemaining(&first, TRUE);
3210 SendToProgram("playother\n", &first);
3216 } else if (gameMode == IcsPlayingBlack) {
3217 if (!WhiteOnMove(forwardMostMove)) {
3218 if (first.sendTime) {
3219 if (first.useColors) {
3220 SendToProgram("white\n", &first);
3222 SendTimeRemaining(&first, FALSE);
3224 if (first.useColors) {
3225 SendToProgram("black\n", &first);
3227 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3228 first.maybeThinking = TRUE;
3230 if (first.usePlayother) {
3231 if (first.sendTime) {
3232 SendTimeRemaining(&first, FALSE);
3234 SendToProgram("playother\n", &first);
3243 if (gameMode == IcsObserving && ics_gamenum == -1) {
3244 /* Moves came from oldmoves or moves command
3245 while we weren't doing anything else.
3247 currentMove = forwardMostMove;
3248 ClearHighlights();/*!!could figure this out*/
3249 flipView = appData.flipView;
3250 DrawPosition(TRUE, boards[currentMove]);
3251 DisplayBothClocks();
3252 sprintf(str, "%s vs. %s",
3253 gameInfo.white, gameInfo.black);
3257 /* Moves were history of an active game */
3258 if (gameInfo.resultDetails != NULL) {
3259 free(gameInfo.resultDetails);
3260 gameInfo.resultDetails = NULL;
3263 HistorySet(parseList, backwardMostMove,
3264 forwardMostMove, currentMove-1);
3265 DisplayMove(currentMove - 1);
3266 if (started == STARTED_MOVES) next_out = i;
3267 started = STARTED_NONE;
3268 ics_getting_history = H_FALSE;
3271 case STARTED_OBSERVE:
3272 started = STARTED_NONE;
3273 SendToICS(ics_prefix);
3274 SendToICS("refresh\n");
3280 if(bookHit) { // [HGM] book: simulate book reply
3281 static char bookMove[MSG_SIZ]; // a bit generous?
3283 programStats.nodes = programStats.depth = programStats.time =
3284 programStats.score = programStats.got_only_move = 0;
3285 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3287 strcpy(bookMove, "move ");
3288 strcat(bookMove, bookHit);
3289 HandleMachineMove(bookMove, &first);
3294 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3295 started == STARTED_HOLDINGS ||
3296 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3297 /* Accumulate characters in move list or board */
3298 parse[parse_pos++] = buf[i];
3301 /* Start of game messages. Mostly we detect start of game
3302 when the first board image arrives. On some versions
3303 of the ICS, though, we need to do a "refresh" after starting
3304 to observe in order to get the current board right away. */
3305 if (looking_at(buf, &i, "Adding game * to observation list")) {
3306 started = STARTED_OBSERVE;
3310 /* Handle auto-observe */
3311 if (appData.autoObserve &&
3312 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3313 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3315 /* Choose the player that was highlighted, if any. */
3316 if (star_match[0][0] == '\033' ||
3317 star_match[1][0] != '\033') {
3318 player = star_match[0];
3320 player = star_match[2];
3322 sprintf(str, "%sobserve %s\n",
3323 ics_prefix, StripHighlightAndTitle(player));
3326 /* Save ratings from notify string */
3327 strcpy(player1Name, star_match[0]);
3328 player1Rating = string_to_rating(star_match[1]);
3329 strcpy(player2Name, star_match[2]);
3330 player2Rating = string_to_rating(star_match[3]);
3332 if (appData.debugMode)
3334 "Ratings from 'Game notification:' %s %d, %s %d\n",
3335 player1Name, player1Rating,
3336 player2Name, player2Rating);
3341 /* Deal with automatic examine mode after a game,
3342 and with IcsObserving -> IcsExamining transition */
3343 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3344 looking_at(buf, &i, "has made you an examiner of game *")) {
3346 int gamenum = atoi(star_match[0]);
3347 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3348 gamenum == ics_gamenum) {
3349 /* We were already playing or observing this game;
3350 no need to refetch history */
3351 gameMode = IcsExamining;
3353 pauseExamForwardMostMove = forwardMostMove;
3354 } else if (currentMove < forwardMostMove) {
3355 ForwardInner(forwardMostMove);
3358 /* I don't think this case really can happen */
3359 SendToICS(ics_prefix);
3360 SendToICS("refresh\n");
3365 /* Error messages */
3366 // if (ics_user_moved) {
3367 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3368 if (looking_at(buf, &i, "Illegal move") ||
3369 looking_at(buf, &i, "Not a legal move") ||
3370 looking_at(buf, &i, "Your king is in check") ||
3371 looking_at(buf, &i, "It isn't your turn") ||
3372 looking_at(buf, &i, "It is not your move")) {
3374 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3375 currentMove = forwardMostMove-1;
3376 DisplayMove(currentMove - 1); /* before DMError */
3377 DrawPosition(FALSE, boards[currentMove]);
3378 SwitchClocks(forwardMostMove-1); // [HGM] race
3379 DisplayBothClocks();
3381 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3387 if (looking_at(buf, &i, "still have time") ||
3388 looking_at(buf, &i, "not out of time") ||
3389 looking_at(buf, &i, "either player is out of time") ||
3390 looking_at(buf, &i, "has timeseal; checking")) {
3391 /* We must have called his flag a little too soon */
3392 whiteFlag = blackFlag = FALSE;
3396 if (looking_at(buf, &i, "added * seconds to") ||
3397 looking_at(buf, &i, "seconds were added to")) {
3398 /* Update the clocks */
3399 SendToICS(ics_prefix);
3400 SendToICS("refresh\n");
3404 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3405 ics_clock_paused = TRUE;
3410 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3411 ics_clock_paused = FALSE;
3416 /* Grab player ratings from the Creating: message.
3417 Note we have to check for the special case when
3418 the ICS inserts things like [white] or [black]. */
3419 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3420 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3422 0 player 1 name (not necessarily white)
3424 2 empty, white, or black (IGNORED)
3425 3 player 2 name (not necessarily black)
3428 The names/ratings are sorted out when the game
3429 actually starts (below).
3431 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3432 player1Rating = string_to_rating(star_match[1]);
3433 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3434 player2Rating = string_to_rating(star_match[4]);
3436 if (appData.debugMode)
3438 "Ratings from 'Creating:' %s %d, %s %d\n",
3439 player1Name, player1Rating,
3440 player2Name, player2Rating);
3445 /* Improved generic start/end-of-game messages */
3446 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3447 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3448 /* If tkind == 0: */
3449 /* star_match[0] is the game number */
3450 /* [1] is the white player's name */
3451 /* [2] is the black player's name */
3452 /* For end-of-game: */
3453 /* [3] is the reason for the game end */
3454 /* [4] is a PGN end game-token, preceded by " " */
3455 /* For start-of-game: */
3456 /* [3] begins with "Creating" or "Continuing" */
3457 /* [4] is " *" or empty (don't care). */
3458 int gamenum = atoi(star_match[0]);
3459 char *whitename, *blackname, *why, *endtoken;
3460 ChessMove endtype = (ChessMove) 0;
3463 whitename = star_match[1];
3464 blackname = star_match[2];
3465 why = star_match[3];
3466 endtoken = star_match[4];
3468 whitename = star_match[1];
3469 blackname = star_match[3];
3470 why = star_match[5];
3471 endtoken = star_match[6];
3474 /* Game start messages */
3475 if (strncmp(why, "Creating ", 9) == 0 ||
3476 strncmp(why, "Continuing ", 11) == 0) {
3477 gs_gamenum = gamenum;
3478 strcpy(gs_kind, strchr(why, ' ') + 1);
3479 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3481 if (appData.zippyPlay) {
3482 ZippyGameStart(whitename, blackname);
3485 partnerBoardValid = FALSE; // [HGM] bughouse
3489 /* Game end messages */
3490 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3491 ics_gamenum != gamenum) {
3494 while (endtoken[0] == ' ') endtoken++;
3495 switch (endtoken[0]) {
3498 endtype = GameUnfinished;
3501 endtype = BlackWins;
3504 if (endtoken[1] == '/')
3505 endtype = GameIsDrawn;
3507 endtype = WhiteWins;
3510 GameEnds(endtype, why, GE_ICS);
3512 if (appData.zippyPlay && first.initDone) {
3513 ZippyGameEnd(endtype, why);
3514 if (first.pr == NULL) {
3515 /* Start the next process early so that we'll
3516 be ready for the next challenge */
3517 StartChessProgram(&first);
3519 /* Send "new" early, in case this command takes
3520 a long time to finish, so that we'll be ready
3521 for the next challenge. */
3522 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3526 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3530 if (looking_at(buf, &i, "Removing game * from observation") ||
3531 looking_at(buf, &i, "no longer observing game *") ||
3532 looking_at(buf, &i, "Game * (*) has no examiners")) {
3533 if (gameMode == IcsObserving &&
3534 atoi(star_match[0]) == ics_gamenum)
3536 /* icsEngineAnalyze */
3537 if (appData.icsEngineAnalyze) {
3544 ics_user_moved = FALSE;
3549 if (looking_at(buf, &i, "no longer examining game *")) {
3550 if (gameMode == IcsExamining &&
3551 atoi(star_match[0]) == ics_gamenum)
3555 ics_user_moved = FALSE;
3560 /* Advance leftover_start past any newlines we find,
3561 so only partial lines can get reparsed */
3562 if (looking_at(buf, &i, "\n")) {
3563 prevColor = curColor;
3564 if (curColor != ColorNormal) {
3565 if (oldi > next_out) {
3566 SendToPlayer(&buf[next_out], oldi - next_out);
3569 Colorize(ColorNormal, FALSE);
3570 curColor = ColorNormal;
3572 if (started == STARTED_BOARD) {
3573 started = STARTED_NONE;
3574 parse[parse_pos] = NULLCHAR;
3575 ParseBoard12(parse);
3578 /* Send premove here */
3579 if (appData.premove) {
3581 if (currentMove == 0 &&
3582 gameMode == IcsPlayingWhite &&
3583 appData.premoveWhite) {
3584 sprintf(str, "%s\n", appData.premoveWhiteText);
3585 if (appData.debugMode)
3586 fprintf(debugFP, "Sending premove:\n");
3588 } else if (currentMove == 1 &&
3589 gameMode == IcsPlayingBlack &&
3590 appData.premoveBlack) {
3591 sprintf(str, "%s\n", appData.premoveBlackText);
3592 if (appData.debugMode)
3593 fprintf(debugFP, "Sending premove:\n");
3595 } else if (gotPremove) {
3597 ClearPremoveHighlights();
3598 if (appData.debugMode)
3599 fprintf(debugFP, "Sending premove:\n");
3600 UserMoveEvent(premoveFromX, premoveFromY,
3601 premoveToX, premoveToY,
3606 /* Usually suppress following prompt */
3607 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3608 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3609 if (looking_at(buf, &i, "*% ")) {
3610 savingComment = FALSE;
3615 } else if (started == STARTED_HOLDINGS) {
3617 char new_piece[MSG_SIZ];
3618 started = STARTED_NONE;
3619 parse[parse_pos] = NULLCHAR;
3620 if (appData.debugMode)
3621 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3622 parse, currentMove);
3623 if (sscanf(parse, " game %d", &gamenum) == 1) {
3624 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3625 if (gameInfo.variant == VariantNormal) {
3626 /* [HGM] We seem to switch variant during a game!
3627 * Presumably no holdings were displayed, so we have
3628 * to move the position two files to the right to
3629 * create room for them!
3631 VariantClass newVariant;
3632 switch(gameInfo.boardWidth) { // base guess on board width
3633 case 9: newVariant = VariantShogi; break;
3634 case 10: newVariant = VariantGreat; break;
3635 default: newVariant = VariantCrazyhouse; break;
3637 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3638 /* Get a move list just to see the header, which
3639 will tell us whether this is really bug or zh */
3640 if (ics_getting_history == H_FALSE) {
3641 ics_getting_history = H_REQUESTED;
3642 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646 new_piece[0] = NULLCHAR;
3647 sscanf(parse, "game %d white [%s black [%s <- %s",
3648 &gamenum, white_holding, black_holding,
3650 white_holding[strlen(white_holding)-1] = NULLCHAR;
3651 black_holding[strlen(black_holding)-1] = NULLCHAR;
3652 /* [HGM] copy holdings to board holdings area */
3653 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3654 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3655 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3657 if (appData.zippyPlay && first.initDone) {
3658 ZippyHoldings(white_holding, black_holding,
3662 if (tinyLayout || smallLayout) {
3663 char wh[16], bh[16];
3664 PackHolding(wh, white_holding);
3665 PackHolding(bh, black_holding);
3666 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3667 gameInfo.white, gameInfo.black);
3669 sprintf(str, "%s [%s] vs. %s [%s]",
3670 gameInfo.white, white_holding,
3671 gameInfo.black, black_holding);
3673 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3674 DrawPosition(FALSE, boards[currentMove]);
3676 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3677 sscanf(parse, "game %d white [%s black [%s <- %s",
3678 &gamenum, white_holding, black_holding,
3680 white_holding[strlen(white_holding)-1] = NULLCHAR;
3681 black_holding[strlen(black_holding)-1] = NULLCHAR;
3682 /* [HGM] copy holdings to partner-board holdings area */
3683 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3684 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3685 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3686 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3687 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3690 /* Suppress following prompt */
3691 if (looking_at(buf, &i, "*% ")) {
3692 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693 savingComment = FALSE;
3701 i++; /* skip unparsed character and loop back */
3704 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 // SendToPlayer(&buf[next_out], i - next_out);
3707 started != STARTED_HOLDINGS && leftover_start > next_out) {
3708 SendToPlayer(&buf[next_out], leftover_start - next_out);
3712 leftover_len = buf_len - leftover_start;
3713 /* if buffer ends with something we couldn't parse,
3714 reparse it after appending the next read */
3716 } else if (count == 0) {
3717 RemoveInputSource(isr);
3718 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3720 DisplayFatalError(_("Error reading from ICS"), error, 1);
3725 /* Board style 12 looks like this:
3727 <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
3729 * The "<12> " is stripped before it gets to this routine. The two
3730 * trailing 0's (flip state and clock ticking) are later addition, and
3731 * some chess servers may not have them, or may have only the first.
3732 * Additional trailing fields may be added in the future.
3735 #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"
3737 #define RELATION_OBSERVING_PLAYED 0
3738 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE 1
3740 #define RELATION_PLAYING_NOTMYMOVE -1
3741 #define RELATION_EXAMINING 2
3742 #define RELATION_ISOLATED_BOARD -3
3743 #define RELATION_STARTING_POSITION -4 /* FICS only */
3746 ParseBoard12(string)
3749 GameMode newGameMode;
3750 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753 char to_play, board_chars[200];
3754 char move_str[500], str[500], elapsed_time[500];
3755 char black[32], white[32];
3757 int prevMove = currentMove;
3760 int fromX, fromY, toX, toY;
3762 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763 char *bookHit = NULL; // [HGM] book
3764 Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3766 fromX = fromY = toX = toY = -1;
3770 if (appData.debugMode)
3771 fprintf(debugFP, _("Parsing board: %s\n"), string);
3773 move_str[0] = NULLCHAR;
3774 elapsed_time[0] = NULLCHAR;
3775 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3777 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778 if(string[i] == ' ') { ranks++; files = 0; }
3780 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3783 for(j = 0; j <i; j++) board_chars[j] = string[j];
3784 board_chars[i] = '\0';
3787 n = sscanf(string, PATTERN, &to_play, &double_push,
3788 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789 &gamenum, white, black, &relation, &basetime, &increment,
3790 &white_stren, &black_stren, &white_time, &black_time,
3791 &moveNum, str, elapsed_time, move_str, &ics_flip,
3795 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796 DisplayError(str, 0);
3800 /* Convert the move number to internal form */
3801 moveNum = (moveNum - 1) * 2;
3802 if (to_play == 'B') moveNum++;
3803 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3810 case RELATION_OBSERVING_PLAYED:
3811 case RELATION_OBSERVING_STATIC:
3812 if (gamenum == -1) {
3813 /* Old ICC buglet */
3814 relation = RELATION_OBSERVING_STATIC;
3816 newGameMode = IcsObserving;
3818 case RELATION_PLAYING_MYMOVE:
3819 case RELATION_PLAYING_NOTMYMOVE:
3821 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822 IcsPlayingWhite : IcsPlayingBlack;
3824 case RELATION_EXAMINING:
3825 newGameMode = IcsExamining;
3827 case RELATION_ISOLATED_BOARD:
3829 /* Just display this board. If user was doing something else,
3830 we will forget about it until the next board comes. */
3831 newGameMode = IcsIdle;
3833 case RELATION_STARTING_POSITION:
3834 newGameMode = gameMode;
3838 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3839 && newGameMode == IcsObserving && appData.bgObserve) {
3840 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3841 for (k = 0; k < ranks; k++) {
3842 for (j = 0; j < files; j++)
3843 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3844 if(gameInfo.holdingsWidth > 1) {
3845 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3846 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3849 CopyBoard(partnerBoard, board);
3850 if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3851 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3852 if(partnerUp) DrawPosition(repaint, partnerBoard);
3853 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3854 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3855 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3856 DisplayMessage(partnerStatus, "");
3857 partnerBoardValid = TRUE;
3861 /* Modify behavior for initial board display on move listing
3864 switch (ics_getting_history) {
3868 case H_GOT_REQ_HEADER:
3869 case H_GOT_UNREQ_HEADER:
3870 /* This is the initial position of the current game */
3871 gamenum = ics_gamenum;
3872 moveNum = 0; /* old ICS bug workaround */
3873 if (to_play == 'B') {
3874 startedFromSetupPosition = TRUE;
3875 blackPlaysFirst = TRUE;
3877 if (forwardMostMove == 0) forwardMostMove = 1;
3878 if (backwardMostMove == 0) backwardMostMove = 1;
3879 if (currentMove == 0) currentMove = 1;
3881 newGameMode = gameMode;
3882 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3884 case H_GOT_UNWANTED_HEADER:
3885 /* This is an initial board that we don't want */
3887 case H_GETTING_MOVES:
3888 /* Should not happen */
3889 DisplayError(_("Error gathering move list: extra board"), 0);
3890 ics_getting_history = H_FALSE;
3894 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3895 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3896 /* [HGM] We seem to have switched variant unexpectedly
3897 * Try to guess new variant from board size
3899 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3900 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3901 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3902 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3903 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3904 if(!weird) newVariant = VariantNormal;
3905 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3906 /* Get a move list just to see the header, which
3907 will tell us whether this is really bug or zh */
3908 if (ics_getting_history == H_FALSE) {
3909 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3910 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3915 /* Take action if this is the first board of a new game, or of a
3916 different game than is currently being displayed. */
3917 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3918 relation == RELATION_ISOLATED_BOARD) {
3920 /* Forget the old game and get the history (if any) of the new one */
3921 if (gameMode != BeginningOfGame) {
3925 if (appData.autoRaiseBoard) BoardToTop();
3927 if (gamenum == -1) {
3928 newGameMode = IcsIdle;
3929 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3930 appData.getMoveList && !reqFlag) {
3931 /* Need to get game history */
3932 ics_getting_history = H_REQUESTED;
3933 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3937 /* Initially flip the board to have black on the bottom if playing
3938 black or if the ICS flip flag is set, but let the user change
3939 it with the Flip View button. */
3940 flipView = appData.autoFlipView ?
3941 (newGameMode == IcsPlayingBlack) || ics_flip :
3944 /* Done with values from previous mode; copy in new ones */
3945 gameMode = newGameMode;
3947 ics_gamenum = gamenum;
3948 if (gamenum == gs_gamenum) {
3949 int klen = strlen(gs_kind);
3950 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3951 sprintf(str, "ICS %s", gs_kind);
3952 gameInfo.event = StrSave(str);
3954 gameInfo.event = StrSave("ICS game");
3956 gameInfo.site = StrSave(appData.icsHost);
3957 gameInfo.date = PGNDate();
3958 gameInfo.round = StrSave("-");
3959 gameInfo.white = StrSave(white);
3960 gameInfo.black = StrSave(black);
3961 timeControl = basetime * 60 * 1000;
3963 timeIncrement = increment * 1000;
3964 movesPerSession = 0;
3965 gameInfo.timeControl = TimeControlTagValue();
3966 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3967 if (appData.debugMode) {
3968 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3969 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3970 setbuf(debugFP, NULL);
3973 gameInfo.outOfBook = NULL;
3975 /* Do we have the ratings? */
3976 if (strcmp(player1Name, white) == 0 &&
3977 strcmp(player2Name, black) == 0) {
3978 if (appData.debugMode)
3979 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3980 player1Rating, player2Rating);
3981 gameInfo.whiteRating = player1Rating;
3982 gameInfo.blackRating = player2Rating;
3983 } else if (strcmp(player2Name, white) == 0 &&
3984 strcmp(player1Name, black) == 0) {
3985 if (appData.debugMode)
3986 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3987 player2Rating, player1Rating);
3988 gameInfo.whiteRating = player2Rating;
3989 gameInfo.blackRating = player1Rating;
3991 player1Name[0] = player2Name[0] = NULLCHAR;
3993 /* Silence shouts if requested */
3994 if (appData.quietPlay &&
3995 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3996 SendToICS(ics_prefix);
3997 SendToICS("set shout 0\n");
4001 /* Deal with midgame name changes */
4003 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4004 if (gameInfo.white) free(gameInfo.white);
4005 gameInfo.white = StrSave(white);
4007 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4008 if (gameInfo.black) free(gameInfo.black);
4009 gameInfo.black = StrSave(black);
4013 /* Throw away game result if anything actually changes in examine mode */
4014 if (gameMode == IcsExamining && !newGame) {
4015 gameInfo.result = GameUnfinished;
4016 if (gameInfo.resultDetails != NULL) {
4017 free(gameInfo.resultDetails);
4018 gameInfo.resultDetails = NULL;
4022 /* In pausing && IcsExamining mode, we ignore boards coming
4023 in if they are in a different variation than we are. */
4024 if (pauseExamInvalid) return;
4025 if (pausing && gameMode == IcsExamining) {
4026 if (moveNum <= pauseExamForwardMostMove) {
4027 pauseExamInvalid = TRUE;
4028 forwardMostMove = pauseExamForwardMostMove;
4033 if (appData.debugMode) {
4034 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4036 /* Parse the board */
4037 for (k = 0; k < ranks; k++) {
4038 for (j = 0; j < files; j++)
4039 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4040 if(gameInfo.holdingsWidth > 1) {
4041 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4042 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4045 CopyBoard(boards[moveNum], board);
4046 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4048 startedFromSetupPosition =
4049 !CompareBoards(board, initialPosition);
4050 if(startedFromSetupPosition)
4051 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4054 /* [HGM] Set castling rights. Take the outermost Rooks,
4055 to make it also work for FRC opening positions. Note that board12
4056 is really defective for later FRC positions, as it has no way to
4057 indicate which Rook can castle if they are on the same side of King.
4058 For the initial position we grant rights to the outermost Rooks,
4059 and remember thos rights, and we then copy them on positions
4060 later in an FRC game. This means WB might not recognize castlings with
4061 Rooks that have moved back to their original position as illegal,
4062 but in ICS mode that is not its job anyway.
4064 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4065 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4067 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4068 if(board[0][i] == WhiteRook) j = i;
4069 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4070 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4071 if(board[0][i] == WhiteRook) j = i;
4072 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4073 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4074 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4075 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4076 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4077 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4078 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4080 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4081 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4082 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4083 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4084 if(board[BOARD_HEIGHT-1][k] == bKing)
4085 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4086 if(gameInfo.variant == VariantTwoKings) {
4087 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4088 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4089 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4092 r = boards[moveNum][CASTLING][0] = initialRights[0];
4093 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4094 r = boards[moveNum][CASTLING][1] = initialRights[1];
4095 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4096 r = boards[moveNum][CASTLING][3] = initialRights[3];
4097 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4098 r = boards[moveNum][CASTLING][4] = initialRights[4];
4099 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4100 /* wildcastle kludge: always assume King has rights */
4101 r = boards[moveNum][CASTLING][2] = initialRights[2];
4102 r = boards[moveNum][CASTLING][5] = initialRights[5];
4104 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4105 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4108 if (ics_getting_history == H_GOT_REQ_HEADER ||
4109 ics_getting_history == H_GOT_UNREQ_HEADER) {
4110 /* This was an initial position from a move list, not
4111 the current position */
4115 /* Update currentMove and known move number limits */
4116 newMove = newGame || moveNum > forwardMostMove;
4119 forwardMostMove = backwardMostMove = currentMove = moveNum;
4120 if (gameMode == IcsExamining && moveNum == 0) {
4121 /* Workaround for ICS limitation: we are not told the wild
4122 type when starting to examine a game. But if we ask for
4123 the move list, the move list header will tell us */
4124 ics_getting_history = H_REQUESTED;
4125 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4128 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4129 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4131 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4132 /* [HGM] applied this also to an engine that is silently watching */
4133 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4134 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4135 gameInfo.variant == currentlyInitializedVariant) {
4136 takeback = forwardMostMove - moveNum;
4137 for (i = 0; i < takeback; i++) {
4138 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4139 SendToProgram("undo\n", &first);
4144 forwardMostMove = moveNum;
4145 if (!pausing || currentMove > forwardMostMove)
4146 currentMove = forwardMostMove;
4148 /* New part of history that is not contiguous with old part */
4149 if (pausing && gameMode == IcsExamining) {
4150 pauseExamInvalid = TRUE;
4151 forwardMostMove = pauseExamForwardMostMove;
4154 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4156 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4157 // [HGM] when we will receive the move list we now request, it will be
4158 // fed to the engine from the first move on. So if the engine is not
4159 // in the initial position now, bring it there.
4160 InitChessProgram(&first, 0);
4163 ics_getting_history = H_REQUESTED;
4164 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4167 forwardMostMove = backwardMostMove = currentMove = moveNum;
4170 /* Update the clocks */
4171 if (strchr(elapsed_time, '.')) {
4173 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4174 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4176 /* Time is in seconds */
4177 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4178 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4183 if (appData.zippyPlay && newGame &&
4184 gameMode != IcsObserving && gameMode != IcsIdle &&
4185 gameMode != IcsExamining)
4186 ZippyFirstBoard(moveNum, basetime, increment);
4189 /* Put the move on the move list, first converting
4190 to canonical algebraic form. */
4192 if (appData.debugMode) {
4193 if (appData.debugMode) { int f = forwardMostMove;
4194 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4195 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4196 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4198 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4199 fprintf(debugFP, "moveNum = %d\n", moveNum);
4200 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4201 setbuf(debugFP, NULL);
4203 if (moveNum <= backwardMostMove) {
4204 /* We don't know what the board looked like before
4206 strcpy(parseList[moveNum - 1], move_str);
4207 strcat(parseList[moveNum - 1], " ");
4208 strcat(parseList[moveNum - 1], elapsed_time);
4209 moveList[moveNum - 1][0] = NULLCHAR;
4210 } else if (strcmp(move_str, "none") == 0) {
4211 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4212 /* Again, we don't know what the board looked like;
4213 this is really the start of the game. */
4214 parseList[moveNum - 1][0] = NULLCHAR;
4215 moveList[moveNum - 1][0] = NULLCHAR;
4216 backwardMostMove = moveNum;
4217 startedFromSetupPosition = TRUE;
4218 fromX = fromY = toX = toY = -1;
4220 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4221 // So we parse the long-algebraic move string in stead of the SAN move
4222 int valid; char buf[MSG_SIZ], *prom;
4224 // str looks something like "Q/a1-a2"; kill the slash
4226 sprintf(buf, "%c%s", str[0], str+2);
4227 else strcpy(buf, str); // might be castling
4228 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4229 strcat(buf, prom); // long move lacks promo specification!
4230 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4231 if(appData.debugMode)
4232 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4233 strcpy(move_str, buf);
4235 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4236 &fromX, &fromY, &toX, &toY, &promoChar)
4237 || ParseOneMove(buf, moveNum - 1, &moveType,
4238 &fromX, &fromY, &toX, &toY, &promoChar);
4239 // end of long SAN patch
4241 (void) CoordsToAlgebraic(boards[moveNum - 1],
4242 PosFlags(moveNum - 1),
4243 fromY, fromX, toY, toX, promoChar,
4244 parseList[moveNum-1]);
4245 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4251 if(gameInfo.variant != VariantShogi)
4252 strcat(parseList[moveNum - 1], "+");
4255 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4256 strcat(parseList[moveNum - 1], "#");
4259 strcat(parseList[moveNum - 1], " ");
4260 strcat(parseList[moveNum - 1], elapsed_time);
4261 /* currentMoveString is set as a side-effect of ParseOneMove */
4262 strcpy(moveList[moveNum - 1], currentMoveString);
4263 strcat(moveList[moveNum - 1], "\n");
4265 /* Move from ICS was illegal!? Punt. */
4266 if (appData.debugMode) {
4267 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4268 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4270 strcpy(parseList[moveNum - 1], move_str);
4271 strcat(parseList[moveNum - 1], " ");
4272 strcat(parseList[moveNum - 1], elapsed_time);
4273 moveList[moveNum - 1][0] = NULLCHAR;
4274 fromX = fromY = toX = toY = -1;
4277 if (appData.debugMode) {
4278 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4279 setbuf(debugFP, NULL);
4283 /* Send move to chess program (BEFORE animating it). */
4284 if (appData.zippyPlay && !newGame && newMove &&
4285 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4287 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4288 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4289 if (moveList[moveNum - 1][0] == NULLCHAR) {
4290 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4292 DisplayError(str, 0);
4294 if (first.sendTime) {
4295 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4297 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4298 if (firstMove && !bookHit) {
4300 if (first.useColors) {
4301 SendToProgram(gameMode == IcsPlayingWhite ?
4303 "black\ngo\n", &first);
4305 SendToProgram("go\n", &first);
4307 first.maybeThinking = TRUE;
4310 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4311 if (moveList[moveNum - 1][0] == NULLCHAR) {
4312 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4313 DisplayError(str, 0);
4315 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4316 SendMoveToProgram(moveNum - 1, &first);
4323 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4324 /* If move comes from a remote source, animate it. If it
4325 isn't remote, it will have already been animated. */
4326 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4327 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4329 if (!pausing && appData.highlightLastMove) {
4330 SetHighlights(fromX, fromY, toX, toY);
4334 /* Start the clocks */
4335 whiteFlag = blackFlag = FALSE;
4336 appData.clockMode = !(basetime == 0 && increment == 0);
4338 ics_clock_paused = TRUE;
4340 } else if (ticking == 1) {
4341 ics_clock_paused = FALSE;
4343 if (gameMode == IcsIdle ||
4344 relation == RELATION_OBSERVING_STATIC ||
4345 relation == RELATION_EXAMINING ||
4347 DisplayBothClocks();
4351 /* Display opponents and material strengths */
4352 if (gameInfo.variant != VariantBughouse &&
4353 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4354 if (tinyLayout || smallLayout) {
4355 if(gameInfo.variant == VariantNormal)
4356 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4357 gameInfo.white, white_stren, gameInfo.black, black_stren,
4358 basetime, increment);
4360 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4361 gameInfo.white, white_stren, gameInfo.black, black_stren,
4362 basetime, increment, (int) gameInfo.variant);
4364 if(gameInfo.variant == VariantNormal)
4365 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4366 gameInfo.white, white_stren, gameInfo.black, black_stren,
4367 basetime, increment);
4369 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4370 gameInfo.white, white_stren, gameInfo.black, black_stren,
4371 basetime, increment, VariantName(gameInfo.variant));
4374 if (appData.debugMode) {
4375 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4380 /* Display the board */
4381 if (!pausing && !appData.noGUI) {
4383 if (appData.premove)
4385 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4386 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4387 ClearPremoveHighlights();
4389 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4390 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4391 DrawPosition(j, boards[currentMove]);
4393 DisplayMove(moveNum - 1);
4394 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4395 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4396 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4397 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4401 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4403 if(bookHit) { // [HGM] book: simulate book reply
4404 static char bookMove[MSG_SIZ]; // a bit generous?
4406 programStats.nodes = programStats.depth = programStats.time =
4407 programStats.score = programStats.got_only_move = 0;
4408 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4410 strcpy(bookMove, "move ");
4411 strcat(bookMove, bookHit);
4412 HandleMachineMove(bookMove, &first);
4421 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4422 ics_getting_history = H_REQUESTED;
4423 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4429 AnalysisPeriodicEvent(force)
4432 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4433 && !force) || !appData.periodicUpdates)
4436 /* Send . command to Crafty to collect stats */
4437 SendToProgram(".\n", &first);
4439 /* Don't send another until we get a response (this makes
4440 us stop sending to old Crafty's which don't understand
4441 the "." command (sending illegal cmds resets node count & time,
4442 which looks bad)) */
4443 programStats.ok_to_send = 0;
4446 void ics_update_width(new_width)
4449 ics_printf("set width %d\n", new_width);
4453 SendMoveToProgram(moveNum, cps)
4455 ChessProgramState *cps;
4459 if (cps->useUsermove) {
4460 SendToProgram("usermove ", cps);
4464 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4465 int len = space - parseList[moveNum];
4466 memcpy(buf, parseList[moveNum], len);
4468 buf[len] = NULLCHAR;
4470 sprintf(buf, "%s\n", parseList[moveNum]);
4472 SendToProgram(buf, cps);
4474 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4475 AlphaRank(moveList[moveNum], 4);
4476 SendToProgram(moveList[moveNum], cps);
4477 AlphaRank(moveList[moveNum], 4); // and back
4479 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4480 * the engine. It would be nice to have a better way to identify castle
4482 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4483 && cps->useOOCastle) {
4484 int fromX = moveList[moveNum][0] - AAA;
4485 int fromY = moveList[moveNum][1] - ONE;
4486 int toX = moveList[moveNum][2] - AAA;
4487 int toY = moveList[moveNum][3] - ONE;
4488 if((boards[moveNum][fromY][fromX] == WhiteKing
4489 && boards[moveNum][toY][toX] == WhiteRook)
4490 || (boards[moveNum][fromY][fromX] == BlackKing
4491 && boards[moveNum][toY][toX] == BlackRook)) {
4492 if(toX > fromX) SendToProgram("O-O\n", cps);
4493 else SendToProgram("O-O-O\n", cps);
4495 else SendToProgram(moveList[moveNum], cps);
4497 else SendToProgram(moveList[moveNum], cps);
4498 /* End of additions by Tord */
4501 /* [HGM] setting up the opening has brought engine in force mode! */
4502 /* Send 'go' if we are in a mode where machine should play. */
4503 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4504 (gameMode == TwoMachinesPlay ||
4506 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4508 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4509 SendToProgram("go\n", cps);
4510 if (appData.debugMode) {
4511 fprintf(debugFP, "(extra)\n");
4514 setboardSpoiledMachineBlack = 0;
4518 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4520 int fromX, fromY, toX, toY;
4522 char user_move[MSG_SIZ];
4526 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4527 (int)moveType, fromX, fromY, toX, toY);
4528 DisplayError(user_move + strlen("say "), 0);
4530 case WhiteKingSideCastle:
4531 case BlackKingSideCastle:
4532 case WhiteQueenSideCastleWild:
4533 case BlackQueenSideCastleWild:
4535 case WhiteHSideCastleFR:
4536 case BlackHSideCastleFR:
4538 sprintf(user_move, "o-o\n");
4540 case WhiteQueenSideCastle:
4541 case BlackQueenSideCastle:
4542 case WhiteKingSideCastleWild:
4543 case BlackKingSideCastleWild:
4545 case WhiteASideCastleFR:
4546 case BlackASideCastleFR:
4548 sprintf(user_move, "o-o-o\n");
4550 case WhitePromotionQueen:
4551 case BlackPromotionQueen:
4552 case WhitePromotionRook:
4553 case BlackPromotionRook:
4554 case WhitePromotionBishop:
4555 case BlackPromotionBishop:
4556 case WhitePromotionKnight:
4557 case BlackPromotionKnight:
4558 case WhitePromotionKing:
4559 case BlackPromotionKing:
4560 case WhitePromotionChancellor:
4561 case BlackPromotionChancellor:
4562 case WhitePromotionArchbishop:
4563 case BlackPromotionArchbishop:
4564 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4565 sprintf(user_move, "%c%c%c%c=%c\n",
4566 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4567 PieceToChar(WhiteFerz));
4568 else if(gameInfo.variant == VariantGreat)
4569 sprintf(user_move, "%c%c%c%c=%c\n",
4570 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4571 PieceToChar(WhiteMan));
4573 sprintf(user_move, "%c%c%c%c=%c\n",
4574 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4575 PieceToChar(PromoPiece(moveType)));
4579 sprintf(user_move, "%c@%c%c\n",
4580 ToUpper(PieceToChar((ChessSquare) fromX)),
4581 AAA + toX, ONE + toY);
4584 case WhiteCapturesEnPassant:
4585 case BlackCapturesEnPassant:
4586 case IllegalMove: /* could be a variant we don't quite understand */
4587 sprintf(user_move, "%c%c%c%c\n",
4588 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4591 SendToICS(user_move);
4592 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4593 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4598 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4599 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4600 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4601 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4602 DisplayError("You cannot do this while you are playing or observing", 0);
4605 if(gameMode != IcsExamining) { // is this ever not the case?
4606 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4608 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4609 sprintf(command, "match %s", ics_handle);
4610 } else { // on FICS we must first go to general examine mode
4611 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4613 if(gameInfo.variant != VariantNormal) {
4614 // try figure out wild number, as xboard names are not always valid on ICS
4615 for(i=1; i<=36; i++) {
4616 sprintf(buf, "wild/%d", i);
4617 if(StringToVariant(buf) == gameInfo.variant) break;
4619 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4620 else if(i == 22) sprintf(buf, "%s fr\n", command);
4621 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4622 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4623 SendToICS(ics_prefix);
4625 if(startedFromSetupPosition || backwardMostMove != 0) {
4626 fen = PositionToFEN(backwardMostMove, NULL);
4627 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4628 sprintf(buf, "loadfen %s\n", fen);
4630 } else { // FICS: everything has to set by separate bsetup commands
4631 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4632 sprintf(buf, "bsetup fen %s\n", fen);
4634 if(!WhiteOnMove(backwardMostMove)) {
4635 SendToICS("bsetup tomove black\n");
4637 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4638 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4640 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4641 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4643 i = boards[backwardMostMove][EP_STATUS];
4644 if(i >= 0) { // set e.p.
4645 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4651 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4652 SendToICS("bsetup done\n"); // switch to normal examining.
4654 for(i = backwardMostMove; i<last; i++) {
4656 sprintf(buf, "%s\n", parseList[i]);
4659 SendToICS(ics_prefix);
4660 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4664 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4669 if (rf == DROP_RANK) {
4670 sprintf(move, "%c@%c%c\n",
4671 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4673 if (promoChar == 'x' || promoChar == NULLCHAR) {
4674 sprintf(move, "%c%c%c%c\n",
4675 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4677 sprintf(move, "%c%c%c%c%c\n",
4678 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4684 ProcessICSInitScript(f)
4689 while (fgets(buf, MSG_SIZ, f)) {
4690 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4697 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4699 AlphaRank(char *move, int n)
4701 // char *p = move, c; int x, y;
4703 if (appData.debugMode) {
4704 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4708 move[2]>='0' && move[2]<='9' &&
4709 move[3]>='a' && move[3]<='x' ) {
4711 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4712 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4714 if(move[0]>='0' && move[0]<='9' &&
4715 move[1]>='a' && move[1]<='x' &&
4716 move[2]>='0' && move[2]<='9' &&
4717 move[3]>='a' && move[3]<='x' ) {
4718 /* input move, Shogi -> normal */
4719 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4720 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4721 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4722 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4725 move[3]>='0' && move[3]<='9' &&
4726 move[2]>='a' && move[2]<='x' ) {
4728 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4729 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4732 move[0]>='a' && move[0]<='x' &&
4733 move[3]>='0' && move[3]<='9' &&
4734 move[2]>='a' && move[2]<='x' ) {
4735 /* output move, normal -> Shogi */
4736 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4737 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4738 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4739 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4740 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4742 if (appData.debugMode) {
4743 fprintf(debugFP, " out = '%s'\n", move);
4747 char yy_textstr[8000];
4749 /* Parser for moves from gnuchess, ICS, or user typein box */
4751 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4754 ChessMove *moveType;
4755 int *fromX, *fromY, *toX, *toY;
4758 if (appData.debugMode) {
4759 fprintf(debugFP, "move to parse: %s\n", move);
4761 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4763 switch (*moveType) {
4764 case WhitePromotionChancellor:
4765 case BlackPromotionChancellor:
4766 case WhitePromotionArchbishop:
4767 case BlackPromotionArchbishop:
4768 case WhitePromotionQueen:
4769 case BlackPromotionQueen:
4770 case WhitePromotionRook:
4771 case BlackPromotionRook:
4772 case WhitePromotionBishop:
4773 case BlackPromotionBishop:
4774 case WhitePromotionKnight:
4775 case BlackPromotionKnight:
4776 case WhitePromotionKing:
4777 case BlackPromotionKing:
4779 case WhiteCapturesEnPassant:
4780 case BlackCapturesEnPassant:
4781 case WhiteKingSideCastle:
4782 case WhiteQueenSideCastle:
4783 case BlackKingSideCastle:
4784 case BlackQueenSideCastle:
4785 case WhiteKingSideCastleWild:
4786 case WhiteQueenSideCastleWild:
4787 case BlackKingSideCastleWild:
4788 case BlackQueenSideCastleWild:
4789 /* Code added by Tord: */
4790 case WhiteHSideCastleFR:
4791 case WhiteASideCastleFR:
4792 case BlackHSideCastleFR:
4793 case BlackASideCastleFR:
4794 /* End of code added by Tord */
4795 case IllegalMove: /* bug or odd chess variant */
4796 *fromX = currentMoveString[0] - AAA;
4797 *fromY = currentMoveString[1] - ONE;
4798 *toX = currentMoveString[2] - AAA;
4799 *toY = currentMoveString[3] - ONE;
4800 *promoChar = currentMoveString[4];
4801 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4802 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4803 if (appData.debugMode) {
4804 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4806 *fromX = *fromY = *toX = *toY = 0;
4809 if (appData.testLegality) {
4810 return (*moveType != IllegalMove);
4812 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4813 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4818 *fromX = *moveType == WhiteDrop ?
4819 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4820 (int) CharToPiece(ToLower(currentMoveString[0]));
4822 *toX = currentMoveString[2] - AAA;
4823 *toY = currentMoveString[3] - ONE;
4824 *promoChar = NULLCHAR;
4828 case ImpossibleMove:
4829 case (ChessMove) 0: /* end of file */
4838 if (appData.debugMode) {
4839 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4842 *fromX = *fromY = *toX = *toY = 0;
4843 *promoChar = NULLCHAR;
4850 ParsePV(char *pv, Boolean storeComments)
4851 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4852 int fromX, fromY, toX, toY; char promoChar;
4857 endPV = forwardMostMove;
4859 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4860 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4861 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4862 if(appData.debugMode){
4863 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);
4865 if(!valid && nr == 0 &&
4866 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4867 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4868 // Hande case where played move is different from leading PV move
4869 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4870 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4871 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4872 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4873 endPV += 2; // if position different, keep this
4874 moveList[endPV-1][0] = fromX + AAA;
4875 moveList[endPV-1][1] = fromY + ONE;
4876 moveList[endPV-1][2] = toX + AAA;
4877 moveList[endPV-1][3] = toY + ONE;
4878 parseList[endPV-1][0] = NULLCHAR;
4879 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4882 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4883 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4884 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4885 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4886 valid++; // allow comments in PV
4890 if(endPV+1 > framePtr) break; // no space, truncate
4893 CopyBoard(boards[endPV], boards[endPV-1]);
4894 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4895 moveList[endPV-1][0] = fromX + AAA;
4896 moveList[endPV-1][1] = fromY + ONE;
4897 moveList[endPV-1][2] = toX + AAA;
4898 moveList[endPV-1][3] = toY + ONE;
4900 CoordsToAlgebraic(boards[endPV - 1],
4901 PosFlags(endPV - 1),
4902 fromY, fromX, toY, toX, promoChar,
4903 parseList[endPV - 1]);
4905 parseList[endPV-1][0] = NULLCHAR;
4907 currentMove = endPV;
4908 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4909 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4910 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4911 DrawPosition(TRUE, boards[currentMove]);
4914 static int lastX, lastY;
4917 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4922 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4923 lastX = x; lastY = y;
4924 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4926 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4927 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4929 do{ while(buf[index] && buf[index] != '\n') index++;
4930 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4932 ParsePV(buf+startPV, FALSE);
4933 *start = startPV; *end = index-1;
4938 LoadPV(int x, int y)
4939 { // called on right mouse click to load PV
4940 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4941 lastX = x; lastY = y;
4942 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4949 if(endPV < 0) return;
4951 currentMove = forwardMostMove;
4952 ClearPremoveHighlights();
4953 DrawPosition(TRUE, boards[currentMove]);
4957 MovePV(int x, int y, int h)
4958 { // step through PV based on mouse coordinates (called on mouse move)
4959 int margin = h>>3, step = 0;
4961 if(endPV < 0) return;
4962 // we must somehow check if right button is still down (might be released off board!)
4963 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4964 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4965 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4967 lastX = x; lastY = y;
4968 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4969 currentMove += step;
4970 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4971 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4972 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4973 DrawPosition(FALSE, boards[currentMove]);
4977 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4978 // All positions will have equal probability, but the current method will not provide a unique
4979 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4985 int piecesLeft[(int)BlackPawn];
4986 int seed, nrOfShuffles;
4988 void GetPositionNumber()
4989 { // sets global variable seed
4992 seed = appData.defaultFrcPosition;
4993 if(seed < 0) { // randomize based on time for negative FRC position numbers
4994 for(i=0; i<50; i++) seed += random();
4995 seed = random() ^ random() >> 8 ^ random() << 8;
4996 if(seed<0) seed = -seed;
5000 int put(Board board, int pieceType, int rank, int n, int shade)
5001 // put the piece on the (n-1)-th empty squares of the given shade
5005 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5006 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5007 board[rank][i] = (ChessSquare) pieceType;
5008 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5010 piecesLeft[pieceType]--;
5018 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5019 // calculate where the next piece goes, (any empty square), and put it there
5023 i = seed % squaresLeft[shade];
5024 nrOfShuffles *= squaresLeft[shade];
5025 seed /= squaresLeft[shade];
5026 put(board, pieceType, rank, i, shade);
5029 void AddTwoPieces(Board board, int pieceType, int rank)
5030 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5032 int i, n=squaresLeft[ANY], j=n-1, k;
5034 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5035 i = seed % k; // pick one
5038 while(i >= j) i -= j--;
5039 j = n - 1 - j; i += j;
5040 put(board, pieceType, rank, j, ANY);
5041 put(board, pieceType, rank, i, ANY);
5044 void SetUpShuffle(Board board, int number)
5048 GetPositionNumber(); nrOfShuffles = 1;
5050 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5051 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5052 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5054 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5056 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5057 p = (int) board[0][i];
5058 if(p < (int) BlackPawn) piecesLeft[p] ++;
5059 board[0][i] = EmptySquare;
5062 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5063 // shuffles restricted to allow normal castling put KRR first
5064 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5065 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5066 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5067 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5068 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5069 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5070 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5071 put(board, WhiteRook, 0, 0, ANY);
5072 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5075 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5076 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5077 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5078 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5079 while(piecesLeft[p] >= 2) {
5080 AddOnePiece(board, p, 0, LITE);
5081 AddOnePiece(board, p, 0, DARK);
5083 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5086 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5087 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5088 // but we leave King and Rooks for last, to possibly obey FRC restriction
5089 if(p == (int)WhiteRook) continue;
5090 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5091 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5094 // now everything is placed, except perhaps King (Unicorn) and Rooks
5096 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5097 // Last King gets castling rights
5098 while(piecesLeft[(int)WhiteUnicorn]) {
5099 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5100 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5103 while(piecesLeft[(int)WhiteKing]) {
5104 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5105 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5110 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5111 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5114 // Only Rooks can be left; simply place them all
5115 while(piecesLeft[(int)WhiteRook]) {
5116 i = put(board, WhiteRook, 0, 0, ANY);
5117 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5120 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5122 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5125 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5126 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5129 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5132 int SetCharTable( char *table, const char * map )
5133 /* [HGM] moved here from winboard.c because of its general usefulness */
5134 /* Basically a safe strcpy that uses the last character as King */
5136 int result = FALSE; int NrPieces;
5138 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5139 && NrPieces >= 12 && !(NrPieces&1)) {
5140 int i; /* [HGM] Accept even length from 12 to 34 */
5142 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5143 for( i=0; i<NrPieces/2-1; i++ ) {
5145 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5147 table[(int) WhiteKing] = map[NrPieces/2-1];
5148 table[(int) BlackKing] = map[NrPieces-1];
5156 void Prelude(Board board)
5157 { // [HGM] superchess: random selection of exo-pieces
5158 int i, j, k; ChessSquare p;
5159 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5161 GetPositionNumber(); // use FRC position number
5163 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5164 SetCharTable(pieceToChar, appData.pieceToCharTable);
5165 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5166 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5169 j = seed%4; seed /= 4;
5170 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5171 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5172 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5173 j = seed%3 + (seed%3 >= j); seed /= 3;
5174 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5175 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5176 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5177 j = seed%3; seed /= 3;
5178 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5179 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5180 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5181 j = seed%2 + (seed%2 >= j); seed /= 2;
5182 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5183 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5184 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5185 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5186 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5187 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5188 put(board, exoPieces[0], 0, 0, ANY);
5189 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5193 InitPosition(redraw)
5196 ChessSquare (* pieces)[BOARD_FILES];
5197 int i, j, pawnRow, overrule,
5198 oldx = gameInfo.boardWidth,
5199 oldy = gameInfo.boardHeight,
5200 oldh = gameInfo.holdingsWidth,
5201 oldv = gameInfo.variant;
5203 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5205 /* [AS] Initialize pv info list [HGM] and game status */
5207 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5208 pvInfoList[i].depth = 0;
5209 boards[i][EP_STATUS] = EP_NONE;
5210 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5213 initialRulePlies = 0; /* 50-move counter start */
5215 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5216 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5220 /* [HGM] logic here is completely changed. In stead of full positions */
5221 /* the initialized data only consist of the two backranks. The switch */
5222 /* selects which one we will use, which is than copied to the Board */
5223 /* initialPosition, which for the rest is initialized by Pawns and */
5224 /* empty squares. This initial position is then copied to boards[0], */
5225 /* possibly after shuffling, so that it remains available. */
5227 gameInfo.holdingsWidth = 0; /* default board sizes */
5228 gameInfo.boardWidth = 8;
5229 gameInfo.boardHeight = 8;
5230 gameInfo.holdingsSize = 0;
5231 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5232 for(i=0; i<BOARD_FILES-2; i++)
5233 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5234 initialPosition[EP_STATUS] = EP_NONE;
5235 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5237 switch (gameInfo.variant) {
5238 case VariantFischeRandom:
5239 shuffleOpenings = TRUE;
5243 case VariantShatranj:
5244 pieces = ShatranjArray;
5245 nrCastlingRights = 0;
5246 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5249 pieces = makrukArray;
5250 nrCastlingRights = 0;
5251 startedFromSetupPosition = TRUE;
5252 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5254 case VariantTwoKings:
5255 pieces = twoKingsArray;
5257 case VariantCapaRandom:
5258 shuffleOpenings = TRUE;
5259 case VariantCapablanca:
5260 pieces = CapablancaArray;
5261 gameInfo.boardWidth = 10;
5262 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5265 pieces = GothicArray;
5266 gameInfo.boardWidth = 10;
5267 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5270 pieces = JanusArray;
5271 gameInfo.boardWidth = 10;
5272 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5273 nrCastlingRights = 6;
5274 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5275 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5276 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5277 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5278 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5279 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5282 pieces = FalconArray;
5283 gameInfo.boardWidth = 10;
5284 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5286 case VariantXiangqi:
5287 pieces = XiangqiArray;
5288 gameInfo.boardWidth = 9;
5289 gameInfo.boardHeight = 10;
5290 nrCastlingRights = 0;
5291 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5294 pieces = ShogiArray;
5295 gameInfo.boardWidth = 9;
5296 gameInfo.boardHeight = 9;
5297 gameInfo.holdingsSize = 7;
5298 nrCastlingRights = 0;
5299 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5301 case VariantCourier:
5302 pieces = CourierArray;
5303 gameInfo.boardWidth = 12;
5304 nrCastlingRights = 0;
5305 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5307 case VariantKnightmate:
5308 pieces = KnightmateArray;
5309 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5312 pieces = fairyArray;
5313 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5316 pieces = GreatArray;
5317 gameInfo.boardWidth = 10;
5318 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5319 gameInfo.holdingsSize = 8;
5323 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5324 gameInfo.holdingsSize = 8;
5325 startedFromSetupPosition = TRUE;
5327 case VariantCrazyhouse:
5328 case VariantBughouse:
5330 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5331 gameInfo.holdingsSize = 5;
5333 case VariantWildCastle:
5335 /* !!?shuffle with kings guaranteed to be on d or e file */
5336 shuffleOpenings = 1;
5338 case VariantNoCastle:
5340 nrCastlingRights = 0;
5341 /* !!?unconstrained back-rank shuffle */
5342 shuffleOpenings = 1;
5347 if(appData.NrFiles >= 0) {
5348 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5349 gameInfo.boardWidth = appData.NrFiles;
5351 if(appData.NrRanks >= 0) {
5352 gameInfo.boardHeight = appData.NrRanks;
5354 if(appData.holdingsSize >= 0) {
5355 i = appData.holdingsSize;
5356 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5357 gameInfo.holdingsSize = i;
5359 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5360 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5361 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5363 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5364 if(pawnRow < 1) pawnRow = 1;
5365 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5367 /* User pieceToChar list overrules defaults */
5368 if(appData.pieceToCharTable != NULL)
5369 SetCharTable(pieceToChar, appData.pieceToCharTable);
5371 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5373 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5374 s = (ChessSquare) 0; /* account holding counts in guard band */
5375 for( i=0; i<BOARD_HEIGHT; i++ )
5376 initialPosition[i][j] = s;
5378 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5379 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5380 initialPosition[pawnRow][j] = WhitePawn;
5381 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5382 if(gameInfo.variant == VariantXiangqi) {
5384 initialPosition[pawnRow][j] =
5385 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5386 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5387 initialPosition[2][j] = WhiteCannon;
5388 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5392 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5394 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5397 initialPosition[1][j] = WhiteBishop;
5398 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5400 initialPosition[1][j] = WhiteRook;
5401 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5404 if( nrCastlingRights == -1) {
5405 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5406 /* This sets default castling rights from none to normal corners */
5407 /* Variants with other castling rights must set them themselves above */
5408 nrCastlingRights = 6;
5410 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5411 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5412 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5413 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5414 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5415 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5418 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5419 if(gameInfo.variant == VariantGreat) { // promotion commoners
5420 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5421 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5422 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5423 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5425 if (appData.debugMode) {
5426 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5428 if(shuffleOpenings) {
5429 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5430 startedFromSetupPosition = TRUE;
5432 if(startedFromPositionFile) {
5433 /* [HGM] loadPos: use PositionFile for every new game */
5434 CopyBoard(initialPosition, filePosition);
5435 for(i=0; i<nrCastlingRights; i++)
5436 initialRights[i] = filePosition[CASTLING][i];
5437 startedFromSetupPosition = TRUE;
5440 CopyBoard(boards[0], initialPosition);
5442 if(oldx != gameInfo.boardWidth ||
5443 oldy != gameInfo.boardHeight ||
5444 oldh != gameInfo.holdingsWidth
5446 || oldv == VariantGothic || // For licensing popups
5447 gameInfo.variant == VariantGothic
5450 || oldv == VariantFalcon ||
5451 gameInfo.variant == VariantFalcon
5454 InitDrawingSizes(-2 ,0);
5457 DrawPosition(TRUE, boards[currentMove]);
5461 SendBoard(cps, moveNum)
5462 ChessProgramState *cps;
5465 char message[MSG_SIZ];
5467 if (cps->useSetboard) {
5468 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5469 sprintf(message, "setboard %s\n", fen);
5470 SendToProgram(message, cps);
5476 /* Kludge to set black to move, avoiding the troublesome and now
5477 * deprecated "black" command.
5479 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5481 SendToProgram("edit\n", cps);
5482 SendToProgram("#\n", cps);
5483 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5484 bp = &boards[moveNum][i][BOARD_LEFT];
5485 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5486 if ((int) *bp < (int) BlackPawn) {
5487 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5489 if(message[0] == '+' || message[0] == '~') {
5490 sprintf(message, "%c%c%c+\n",
5491 PieceToChar((ChessSquare)(DEMOTED *bp)),
5494 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5495 message[1] = BOARD_RGHT - 1 - j + '1';
5496 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5498 SendToProgram(message, cps);
5503 SendToProgram("c\n", cps);
5504 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5505 bp = &boards[moveNum][i][BOARD_LEFT];
5506 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5507 if (((int) *bp != (int) EmptySquare)
5508 && ((int) *bp >= (int) BlackPawn)) {
5509 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5511 if(message[0] == '+' || message[0] == '~') {
5512 sprintf(message, "%c%c%c+\n",
5513 PieceToChar((ChessSquare)(DEMOTED *bp)),
5516 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5517 message[1] = BOARD_RGHT - 1 - j + '1';
5518 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5520 SendToProgram(message, cps);
5525 SendToProgram(".\n", cps);
5527 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5530 static int autoQueen; // [HGM] oneclick
5533 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5535 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5536 /* [HGM] add Shogi promotions */
5537 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5542 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5543 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5545 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5546 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5549 piece = boards[currentMove][fromY][fromX];
5550 if(gameInfo.variant == VariantShogi) {
5551 promotionZoneSize = 3;
5552 highestPromotingPiece = (int)WhiteFerz;
5553 } else if(gameInfo.variant == VariantMakruk) {
5554 promotionZoneSize = 3;
5557 // next weed out all moves that do not touch the promotion zone at all
5558 if((int)piece >= BlackPawn) {
5559 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5561 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5563 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5564 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5567 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5569 // weed out mandatory Shogi promotions
5570 if(gameInfo.variant == VariantShogi) {
5571 if(piece >= BlackPawn) {
5572 if(toY == 0 && piece == BlackPawn ||
5573 toY == 0 && piece == BlackQueen ||
5574 toY <= 1 && piece == BlackKnight) {
5579 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5580 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5581 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5588 // weed out obviously illegal Pawn moves
5589 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5590 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5591 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5592 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5593 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5594 // note we are not allowed to test for valid (non-)capture, due to premove
5597 // we either have a choice what to promote to, or (in Shogi) whether to promote
5598 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5599 *promoChoice = PieceToChar(BlackFerz); // no choice
5602 if(autoQueen) { // predetermined
5603 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5604 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5605 else *promoChoice = PieceToChar(BlackQueen);
5609 // suppress promotion popup on illegal moves that are not premoves
5610 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5611 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5612 if(appData.testLegality && !premove) {
5613 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5614 fromY, fromX, toY, toX, NULLCHAR);
5615 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5616 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5624 InPalace(row, column)
5626 { /* [HGM] for Xiangqi */
5627 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5628 column < (BOARD_WIDTH + 4)/2 &&
5629 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5634 PieceForSquare (x, y)
5638 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5641 return boards[currentMove][y][x];
5645 OKToStartUserMove(x, y)
5648 ChessSquare from_piece;
5651 if (matchMode) return FALSE;
5652 if (gameMode == EditPosition) return TRUE;
5654 if (x >= 0 && y >= 0)
5655 from_piece = boards[currentMove][y][x];
5657 from_piece = EmptySquare;
5659 if (from_piece == EmptySquare) return FALSE;
5661 white_piece = (int)from_piece >= (int)WhitePawn &&
5662 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5665 case PlayFromGameFile:
5667 case TwoMachinesPlay:
5675 case MachinePlaysWhite:
5676 case IcsPlayingBlack:
5677 if (appData.zippyPlay) return FALSE;
5679 DisplayMoveError(_("You are playing Black"));
5684 case MachinePlaysBlack:
5685 case IcsPlayingWhite:
5686 if (appData.zippyPlay) return FALSE;
5688 DisplayMoveError(_("You are playing White"));
5694 if (!white_piece && WhiteOnMove(currentMove)) {
5695 DisplayMoveError(_("It is White's turn"));
5698 if (white_piece && !WhiteOnMove(currentMove)) {
5699 DisplayMoveError(_("It is Black's turn"));
5702 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5703 /* Editing correspondence game history */
5704 /* Could disallow this or prompt for confirmation */
5709 case BeginningOfGame:
5710 if (appData.icsActive) return FALSE;
5711 if (!appData.noChessProgram) {
5713 DisplayMoveError(_("You are playing White"));
5720 if (!white_piece && WhiteOnMove(currentMove)) {
5721 DisplayMoveError(_("It is White's turn"));
5724 if (white_piece && !WhiteOnMove(currentMove)) {
5725 DisplayMoveError(_("It is Black's turn"));
5734 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5735 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5736 && gameMode != AnalyzeFile && gameMode != Training) {
5737 DisplayMoveError(_("Displayed position is not current"));
5744 OnlyMove(int *x, int *y, Boolean captures) {
5745 DisambiguateClosure cl;
5746 if (appData.zippyPlay) return FALSE;
5748 case MachinePlaysBlack:
5749 case IcsPlayingWhite:
5750 case BeginningOfGame:
5751 if(!WhiteOnMove(currentMove)) return FALSE;
5753 case MachinePlaysWhite:
5754 case IcsPlayingBlack:
5755 if(WhiteOnMove(currentMove)) return FALSE;
5760 cl.pieceIn = EmptySquare;
5765 cl.promoCharIn = NULLCHAR;
5766 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5767 if( cl.kind == NormalMove ||
5768 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5769 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5770 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5771 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5778 if(cl.kind != ImpossibleMove) return FALSE;
5779 cl.pieceIn = EmptySquare;
5784 cl.promoCharIn = NULLCHAR;
5785 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5786 if( cl.kind == NormalMove ||
5787 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5788 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5789 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5790 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5795 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5801 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5802 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5803 int lastLoadGameUseList = FALSE;
5804 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5805 ChessMove lastLoadGameStart = (ChessMove) 0;
5808 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5809 int fromX, fromY, toX, toY;
5814 ChessSquare pdown, pup;
5816 /* Check if the user is playing in turn. This is complicated because we
5817 let the user "pick up" a piece before it is his turn. So the piece he
5818 tried to pick up may have been captured by the time he puts it down!
5819 Therefore we use the color the user is supposed to be playing in this
5820 test, not the color of the piece that is currently on the starting
5821 square---except in EditGame mode, where the user is playing both
5822 sides; fortunately there the capture race can't happen. (It can
5823 now happen in IcsExamining mode, but that's just too bad. The user
5824 will get a somewhat confusing message in that case.)
5828 case PlayFromGameFile:
5830 case TwoMachinesPlay:
5834 /* We switched into a game mode where moves are not accepted,
5835 perhaps while the mouse button was down. */
5836 return ImpossibleMove;
5838 case MachinePlaysWhite:
5839 /* User is moving for Black */
5840 if (WhiteOnMove(currentMove)) {
5841 DisplayMoveError(_("It is White's turn"));
5842 return ImpossibleMove;
5846 case MachinePlaysBlack:
5847 /* User is moving for White */
5848 if (!WhiteOnMove(currentMove)) {
5849 DisplayMoveError(_("It is Black's turn"));
5850 return ImpossibleMove;
5856 case BeginningOfGame:
5859 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5860 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5861 /* User is moving for Black */
5862 if (WhiteOnMove(currentMove)) {
5863 DisplayMoveError(_("It is White's turn"));
5864 return ImpossibleMove;
5867 /* User is moving for White */
5868 if (!WhiteOnMove(currentMove)) {
5869 DisplayMoveError(_("It is Black's turn"));
5870 return ImpossibleMove;
5875 case IcsPlayingBlack:
5876 /* User is moving for Black */
5877 if (WhiteOnMove(currentMove)) {
5878 if (!appData.premove) {
5879 DisplayMoveError(_("It is White's turn"));
5880 } else if (toX >= 0 && toY >= 0) {
5883 premoveFromX = fromX;
5884 premoveFromY = fromY;
5885 premovePromoChar = promoChar;
5887 if (appData.debugMode)
5888 fprintf(debugFP, "Got premove: fromX %d,"
5889 "fromY %d, toX %d, toY %d\n",
5890 fromX, fromY, toX, toY);
5892 return ImpossibleMove;
5896 case IcsPlayingWhite:
5897 /* User is moving for White */
5898 if (!WhiteOnMove(currentMove)) {
5899 if (!appData.premove) {
5900 DisplayMoveError(_("It is Black's turn"));
5901 } else if (toX >= 0 && toY >= 0) {
5904 premoveFromX = fromX;
5905 premoveFromY = fromY;
5906 premovePromoChar = promoChar;
5908 if (appData.debugMode)
5909 fprintf(debugFP, "Got premove: fromX %d,"
5910 "fromY %d, toX %d, toY %d\n",
5911 fromX, fromY, toX, toY);
5913 return ImpossibleMove;
5921 /* EditPosition, empty square, or different color piece;
5922 click-click move is possible */
5923 if (toX == -2 || toY == -2) {
5924 boards[0][fromY][fromX] = EmptySquare;
5925 return AmbiguousMove;
5926 } else if (toX >= 0 && toY >= 0) {
5927 boards[0][toY][toX] = boards[0][fromY][fromX];
5928 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5929 if(boards[0][fromY][0] != EmptySquare) {
5930 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5931 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5934 if(fromX == BOARD_RGHT+1) {
5935 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5936 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5937 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5940 boards[0][fromY][fromX] = EmptySquare;
5941 return AmbiguousMove;
5943 return ImpossibleMove;
5946 if(toX < 0 || toY < 0) return ImpossibleMove;
5947 pdown = boards[currentMove][fromY][fromX];
5948 pup = boards[currentMove][toY][toX];
5950 /* [HGM] If move started in holdings, it means a drop */
5951 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5952 if( pup != EmptySquare ) return ImpossibleMove;
5953 if(appData.testLegality) {
5954 /* it would be more logical if LegalityTest() also figured out
5955 * which drops are legal. For now we forbid pawns on back rank.
5956 * Shogi is on its own here...
5958 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5959 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5960 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5962 return WhiteDrop; /* Not needed to specify white or black yet */
5965 /* [HGM] always test for legality, to get promotion info */
5966 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5967 fromY, fromX, toY, toX, promoChar);
5968 /* [HGM] but possibly ignore an IllegalMove result */
5969 if (appData.testLegality) {
5970 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5971 DisplayMoveError(_("Illegal move"));
5972 return ImpossibleMove;
5977 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5978 function is made into one that returns an OK move type if FinishMove
5979 should be called. This to give the calling driver routine the
5980 opportunity to finish the userMove input with a promotion popup,
5981 without bothering the user with this for invalid or illegal moves */
5983 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5986 /* Common tail of UserMoveEvent and DropMenuEvent */
5988 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5990 int fromX, fromY, toX, toY;
5991 /*char*/int promoChar;
5995 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5996 // [HGM] superchess: suppress promotions to non-available piece
5997 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5998 if(WhiteOnMove(currentMove)) {
5999 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6001 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6005 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6006 move type in caller when we know the move is a legal promotion */
6007 if(moveType == NormalMove && promoChar)
6008 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6010 /* [HGM] convert drag-and-drop piece drops to standard form */
6011 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6012 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6013 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6014 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6015 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6016 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6017 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6018 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6022 /* [HGM] <popupFix> The following if has been moved here from
6023 UserMoveEvent(). Because it seemed to belong here (why not allow
6024 piece drops in training games?), and because it can only be
6025 performed after it is known to what we promote. */
6026 if (gameMode == Training) {
6027 /* compare the move played on the board to the next move in the
6028 * game. If they match, display the move and the opponent's response.
6029 * If they don't match, display an error message.
6033 CopyBoard(testBoard, boards[currentMove]);
6034 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6036 if (CompareBoards(testBoard, boards[currentMove+1])) {
6037 ForwardInner(currentMove+1);
6039 /* Autoplay the opponent's response.
6040 * if appData.animate was TRUE when Training mode was entered,
6041 * the response will be animated.
6043 saveAnimate = appData.animate;
6044 appData.animate = animateTraining;
6045 ForwardInner(currentMove+1);
6046 appData.animate = saveAnimate;
6048 /* check for the end of the game */
6049 if (currentMove >= forwardMostMove) {
6050 gameMode = PlayFromGameFile;
6052 SetTrainingModeOff();
6053 DisplayInformation(_("End of game"));
6056 DisplayError(_("Incorrect move"), 0);
6061 /* Ok, now we know that the move is good, so we can kill
6062 the previous line in Analysis Mode */
6063 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6064 && currentMove < forwardMostMove) {
6065 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6068 /* If we need the chess program but it's dead, restart it */
6069 ResurrectChessProgram();
6071 /* A user move restarts a paused game*/
6075 thinkOutput[0] = NULLCHAR;
6077 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6079 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6081 if (gameMode == BeginningOfGame) {
6082 if (appData.noChessProgram) {
6083 gameMode = EditGame;
6087 gameMode = MachinePlaysBlack;
6090 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6092 if (first.sendName) {
6093 sprintf(buf, "name %s\n", gameInfo.white);
6094 SendToProgram(buf, &first);
6101 /* Relay move to ICS or chess engine */
6102 if (appData.icsActive) {
6103 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6104 gameMode == IcsExamining) {
6105 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6106 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6108 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6110 // also send plain move, in case ICS does not understand atomic claims
6111 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6115 if (first.sendTime && (gameMode == BeginningOfGame ||
6116 gameMode == MachinePlaysWhite ||
6117 gameMode == MachinePlaysBlack)) {
6118 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6120 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6121 // [HGM] book: if program might be playing, let it use book
6122 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6123 first.maybeThinking = TRUE;
6124 } else SendMoveToProgram(forwardMostMove-1, &first);
6125 if (currentMove == cmailOldMove + 1) {
6126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6130 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6134 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6140 if (WhiteOnMove(currentMove)) {
6141 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6143 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6147 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6152 case MachinePlaysBlack:
6153 case MachinePlaysWhite:
6154 /* disable certain menu options while machine is thinking */
6155 SetMachineThinkingEnables();
6162 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6164 if(bookHit) { // [HGM] book: simulate book reply
6165 static char bookMove[MSG_SIZ]; // a bit generous?
6167 programStats.nodes = programStats.depth = programStats.time =
6168 programStats.score = programStats.got_only_move = 0;
6169 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6171 strcpy(bookMove, "move ");
6172 strcat(bookMove, bookHit);
6173 HandleMachineMove(bookMove, &first);
6179 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6180 int fromX, fromY, toX, toY;
6183 /* [HGM] This routine was added to allow calling of its two logical
6184 parts from other modules in the old way. Before, UserMoveEvent()
6185 automatically called FinishMove() if the move was OK, and returned
6186 otherwise. I separated the two, in order to make it possible to
6187 slip a promotion popup in between. But that it always needs two
6188 calls, to the first part, (now called UserMoveTest() ), and to
6189 FinishMove if the first part succeeded. Calls that do not need
6190 to do anything in between, can call this routine the old way.
6192 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6193 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6194 if(moveType == AmbiguousMove)
6195 DrawPosition(FALSE, boards[currentMove]);
6196 else if(moveType != ImpossibleMove && moveType != Comment)
6197 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6201 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6208 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6209 Markers *m = (Markers *) closure;
6210 if(rf == fromY && ff == fromX)
6211 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6212 || kind == WhiteCapturesEnPassant
6213 || kind == BlackCapturesEnPassant);
6214 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6218 MarkTargetSquares(int clear)
6221 if(!appData.markers || !appData.highlightDragging ||
6222 !appData.testLegality || gameMode == EditPosition) return;
6224 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6227 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6228 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6229 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6231 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6234 DrawPosition(TRUE, NULL);
6237 void LeftClick(ClickType clickType, int xPix, int yPix)
6240 Boolean saveAnimate;
6241 static int second = 0, promotionChoice = 0;
6242 char promoChoice = NULLCHAR;
6244 if(appData.seekGraph && appData.icsActive && loggedOn &&
6245 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6246 SeekGraphClick(clickType, xPix, yPix, 0);
6250 if (clickType == Press) ErrorPopDown();
6251 MarkTargetSquares(1);
6253 x = EventToSquare(xPix, BOARD_WIDTH);
6254 y = EventToSquare(yPix, BOARD_HEIGHT);
6255 if (!flipView && y >= 0) {
6256 y = BOARD_HEIGHT - 1 - y;
6258 if (flipView && x >= 0) {
6259 x = BOARD_WIDTH - 1 - x;
6262 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6263 if(clickType == Release) return; // ignore upclick of click-click destination
6264 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6265 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6266 if(gameInfo.holdingsWidth &&
6267 (WhiteOnMove(currentMove)
6268 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6269 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6270 // click in right holdings, for determining promotion piece
6271 ChessSquare p = boards[currentMove][y][x];
6272 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6273 if(p != EmptySquare) {
6274 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6279 DrawPosition(FALSE, boards[currentMove]);
6283 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6284 if(clickType == Press
6285 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6286 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6287 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6290 autoQueen = appData.alwaysPromoteToQueen;
6293 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6294 if (clickType == Press) {
6296 if (OKToStartUserMove(x, y)) {
6300 MarkTargetSquares(0);
6301 DragPieceBegin(xPix, yPix);
6302 if (appData.highlightDragging) {
6303 SetHighlights(x, y, -1, -1);
6312 if (clickType == Press && gameMode != EditPosition) {
6317 // ignore off-board to clicks
6318 if(y < 0 || x < 0) return;
6320 /* Check if clicking again on the same color piece */
6321 fromP = boards[currentMove][fromY][fromX];
6322 toP = boards[currentMove][y][x];
6323 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6324 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6325 WhitePawn <= toP && toP <= WhiteKing &&
6326 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6327 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6328 (BlackPawn <= fromP && fromP <= BlackKing &&
6329 BlackPawn <= toP && toP <= BlackKing &&
6330 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6331 !(fromP == BlackKing && toP == BlackRook && frc))) {
6332 /* Clicked again on same color piece -- changed his mind */
6333 second = (x == fromX && y == fromY);
6334 if(!second || !OnlyMove(&x, &y, TRUE)) {
6335 if (appData.highlightDragging) {
6336 SetHighlights(x, y, -1, -1);
6340 if (OKToStartUserMove(x, y)) {
6343 MarkTargetSquares(0);
6344 DragPieceBegin(xPix, yPix);
6349 // ignore clicks on holdings
6350 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6353 if (clickType == Release && x == fromX && y == fromY) {
6354 DragPieceEnd(xPix, yPix);
6355 if (appData.animateDragging) {
6356 /* Undo animation damage if any */
6357 DrawPosition(FALSE, NULL);
6360 /* Second up/down in same square; just abort move */
6365 ClearPremoveHighlights();
6367 /* First upclick in same square; start click-click mode */
6368 SetHighlights(x, y, -1, -1);
6373 /* we now have a different from- and (possibly off-board) to-square */
6374 /* Completed move */
6377 saveAnimate = appData.animate;
6378 if (clickType == Press) {
6379 /* Finish clickclick move */
6380 if (appData.animate || appData.highlightLastMove) {
6381 SetHighlights(fromX, fromY, toX, toY);
6386 /* Finish drag move */
6387 if (appData.highlightLastMove) {
6388 SetHighlights(fromX, fromY, toX, toY);
6392 DragPieceEnd(xPix, yPix);
6393 /* Don't animate move and drag both */
6394 appData.animate = FALSE;
6397 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6398 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6399 ChessSquare piece = boards[currentMove][fromY][fromX];
6400 if(gameMode == EditPosition && piece != EmptySquare &&
6401 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6404 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6405 n = PieceToNumber(piece - (int)BlackPawn);
6406 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6407 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6408 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6410 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6411 n = PieceToNumber(piece);
6412 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6413 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6414 boards[currentMove][n][BOARD_WIDTH-2]++;
6416 boards[currentMove][fromY][fromX] = EmptySquare;
6420 DrawPosition(TRUE, boards[currentMove]);
6424 // off-board moves should not be highlighted
6425 if(x < 0 || x < 0) ClearHighlights();
6427 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6428 SetHighlights(fromX, fromY, toX, toY);
6429 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6430 // [HGM] super: promotion to captured piece selected from holdings
6431 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6432 promotionChoice = TRUE;
6433 // kludge follows to temporarily execute move on display, without promoting yet
6434 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6435 boards[currentMove][toY][toX] = p;
6436 DrawPosition(FALSE, boards[currentMove]);
6437 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6438 boards[currentMove][toY][toX] = q;
6439 DisplayMessage("Click in holdings to choose piece", "");
6444 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6445 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6446 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6449 appData.animate = saveAnimate;
6450 if (appData.animate || appData.animateDragging) {
6451 /* Undo animation damage if needed */
6452 DrawPosition(FALSE, NULL);
6456 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6457 { // front-end-free part taken out of PieceMenuPopup
6458 int whichMenu; int xSqr, ySqr;
6460 if(seekGraphUp) { // [HGM] seekgraph
6461 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6462 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6466 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6467 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6468 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6469 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6470 if(action == Press) {
6471 originalFlip = flipView;
6472 flipView = !flipView; // temporarily flip board to see game from partners perspective
6473 DrawPosition(TRUE, partnerBoard);
6474 DisplayMessage(partnerStatus, "");
6476 } else if(action == Release) {
6477 flipView = originalFlip;
6478 DrawPosition(TRUE, boards[currentMove]);
6484 xSqr = EventToSquare(x, BOARD_WIDTH);
6485 ySqr = EventToSquare(y, BOARD_HEIGHT);
6486 if (action == Release) UnLoadPV(); // [HGM] pv
6487 if (action != Press) return -2; // return code to be ignored
6490 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6492 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6493 if (xSqr < 0 || ySqr < 0) return -1;
\r
6494 whichMenu = 0; // edit-position menu
6497 if(!appData.icsEngineAnalyze) return -1;
6498 case IcsPlayingWhite:
6499 case IcsPlayingBlack:
6500 if(!appData.zippyPlay) goto noZip;
6503 case MachinePlaysWhite:
6504 case MachinePlaysBlack:
6505 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6506 if (!appData.dropMenu) {
6508 return 2; // flag front-end to grab mouse events
6510 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6511 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6514 if (xSqr < 0 || ySqr < 0) return -1;
6515 if (!appData.dropMenu || appData.testLegality &&
6516 gameInfo.variant != VariantBughouse &&
6517 gameInfo.variant != VariantCrazyhouse) return -1;
6518 whichMenu = 1; // drop menu
6524 if (((*fromX = xSqr) < 0) ||
6525 ((*fromY = ySqr) < 0)) {
6526 *fromX = *fromY = -1;
6530 *fromX = BOARD_WIDTH - 1 - *fromX;
6532 *fromY = BOARD_HEIGHT - 1 - *fromY;
6537 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6539 // char * hint = lastHint;
6540 FrontEndProgramStats stats;
6542 stats.which = cps == &first ? 0 : 1;
6543 stats.depth = cpstats->depth;
6544 stats.nodes = cpstats->nodes;
6545 stats.score = cpstats->score;
6546 stats.time = cpstats->time;
6547 stats.pv = cpstats->movelist;
6548 stats.hint = lastHint;
6549 stats.an_move_index = 0;
6550 stats.an_move_count = 0;
6552 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6553 stats.hint = cpstats->move_name;
6554 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6555 stats.an_move_count = cpstats->nr_moves;
6558 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6560 SetProgramStats( &stats );
6564 Adjudicate(ChessProgramState *cps)
6565 { // [HGM] some adjudications useful with buggy engines
6566 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6567 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6568 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6569 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6570 int k, count = 0; static int bare = 1;
6571 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6572 Boolean canAdjudicate = !appData.icsActive;
6574 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6575 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6576 if( appData.testLegality )
6577 { /* [HGM] Some more adjudications for obstinate engines */
6578 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6579 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6580 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6581 static int moveCount = 6;
6583 char *reason = NULL;
6585 /* Count what is on board. */
6586 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6587 { ChessSquare p = boards[forwardMostMove][i][j];
6591 { /* count B,N,R and other of each side */
6594 NrK++; break; // [HGM] atomic: count Kings
6598 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6599 bishopsColor |= 1 << ((i^j)&1);
6604 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6605 bishopsColor |= 1 << ((i^j)&1);
6620 PawnAdvance += m; NrPawns++;
6622 NrPieces += (p != EmptySquare);
6623 NrW += ((int)p < (int)BlackPawn);
6624 if(gameInfo.variant == VariantXiangqi &&
6625 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6626 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6627 NrW -= ((int)p < (int)BlackPawn);
6631 /* Some material-based adjudications that have to be made before stalemate test */
6632 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6633 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6634 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6635 if(canAdjudicate && appData.checkMates) {
6637 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6638 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6640 "Xboard adjudication: King destroyed", GE_XBOARD );
6645 /* Bare King in Shatranj (loses) or Losers (wins) */
6646 if( NrW == 1 || NrPieces - NrW == 1) {
6647 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6648 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6649 if(canAdjudicate && appData.checkMates) {
6651 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6652 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6653 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6654 "Xboard adjudication: Bare king", GE_XBOARD );
6658 if( gameInfo.variant == VariantShatranj && --bare < 0)
6660 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6661 if(canAdjudicate && appData.checkMates) {
6662 /* but only adjudicate if adjudication enabled */
6664 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6665 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6666 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6667 "Xboard adjudication: Bare king", GE_XBOARD );
6674 // don't wait for engine to announce game end if we can judge ourselves
6675 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6677 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6678 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6679 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6680 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6683 reason = "Xboard adjudication: 3rd check";
6684 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6694 reason = "Xboard adjudication: Stalemate";
6695 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6696 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6697 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6698 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6699 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6700 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6701 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6702 EP_CHECKMATE : EP_WINS);
6703 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6704 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6708 reason = "Xboard adjudication: Checkmate";
6709 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6713 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6715 result = GameIsDrawn; break;
6717 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6719 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6721 result = (ChessMove) 0;
6723 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6725 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6726 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727 GameEnds( result, reason, GE_XBOARD );
6731 /* Next absolutely insufficient mating material. */
6732 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6733 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6734 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6735 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6736 { /* KBK, KNK, KK of KBKB with like Bishops */
6738 /* always flag draws, for judging claims */
6739 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6741 if(canAdjudicate && appData.materialDraws) {
6742 /* but only adjudicate them if adjudication enabled */
6743 if(engineOpponent) {
6744 SendToProgram("force\n", engineOpponent); // suppress reply
6745 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6747 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6748 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6753 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6755 ( NrWR == 1 && NrBR == 1 /* KRKR */
6756 || NrWQ==1 && NrBQ==1 /* KQKQ */
6757 || NrWN==2 || NrBN==2 /* KNNK */
6758 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6760 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6761 { /* if the first 3 moves do not show a tactical win, declare draw */
6762 if(engineOpponent) {
6763 SendToProgram("force\n", engineOpponent); // suppress reply
6764 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6766 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6767 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6770 } else moveCount = 6;
6774 if (appData.debugMode) { int i;
6775 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6776 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6777 appData.drawRepeats);
6778 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6779 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6783 // Repetition draws and 50-move rule can be applied independently of legality testing
6785 /* Check for rep-draws */
6787 for(k = forwardMostMove-2;
6788 k>=backwardMostMove && k>=forwardMostMove-100 &&
6789 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6790 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6793 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6794 /* compare castling rights */
6795 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6796 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6797 rights++; /* King lost rights, while rook still had them */
6798 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6799 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6800 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6801 rights++; /* but at least one rook lost them */
6803 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6804 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6806 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6807 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6808 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6811 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6812 && appData.drawRepeats > 1) {
6813 /* adjudicate after user-specified nr of repeats */
6814 if(engineOpponent) {
6815 SendToProgram("force\n", engineOpponent); // suppress reply
6816 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6818 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6819 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6820 // [HGM] xiangqi: check for forbidden perpetuals
6821 int m, ourPerpetual = 1, hisPerpetual = 1;
6822 for(m=forwardMostMove; m>k; m-=2) {
6823 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6824 ourPerpetual = 0; // the current mover did not always check
6825 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6826 hisPerpetual = 0; // the opponent did not always check
6828 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6829 ourPerpetual, hisPerpetual);
6830 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6831 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6832 "Xboard adjudication: perpetual checking", GE_XBOARD );
6835 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6836 break; // (or we would have caught him before). Abort repetition-checking loop.
6837 // Now check for perpetual chases
6838 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6839 hisPerpetual = PerpetualChase(k, forwardMostMove);
6840 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6841 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6842 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6843 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6846 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6847 break; // Abort repetition-checking loop.
6849 // if neither of us is checking or chasing all the time, or both are, it is draw
6851 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6854 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6855 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6859 /* Now we test for 50-move draws. Determine ply count */
6860 count = forwardMostMove;
6861 /* look for last irreversble move */
6862 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6864 /* if we hit starting position, add initial plies */
6865 if( count == backwardMostMove )
6866 count -= initialRulePlies;
6867 count = forwardMostMove - count;
6869 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6870 /* this is used to judge if draw claims are legal */
6871 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6872 if(engineOpponent) {
6873 SendToProgram("force\n", engineOpponent); // suppress reply
6874 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6876 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6877 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6881 /* if draw offer is pending, treat it as a draw claim
6882 * when draw condition present, to allow engines a way to
6883 * claim draws before making their move to avoid a race
6884 * condition occurring after their move
6886 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6888 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6889 p = "Draw claim: 50-move rule";
6890 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6891 p = "Draw claim: 3-fold repetition";
6892 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6893 p = "Draw claim: insufficient mating material";
6894 if( p != NULL && canAdjudicate) {
6895 if(engineOpponent) {
6896 SendToProgram("force\n", engineOpponent); // suppress reply
6897 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6899 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6900 GameEnds( GameIsDrawn, p, GE_XBOARD );
6905 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6906 if(engineOpponent) {
6907 SendToProgram("force\n", engineOpponent); // suppress reply
6908 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6910 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6911 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6917 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6918 { // [HGM] book: this routine intercepts moves to simulate book replies
6919 char *bookHit = NULL;
6921 //first determine if the incoming move brings opponent into his book
6922 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6923 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6924 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6925 if(bookHit != NULL && !cps->bookSuspend) {
6926 // make sure opponent is not going to reply after receiving move to book position
6927 SendToProgram("force\n", cps);
6928 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6930 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6931 // now arrange restart after book miss
6933 // after a book hit we never send 'go', and the code after the call to this routine
6934 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6936 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6937 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6938 SendToProgram(buf, cps);
6939 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6940 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6941 SendToProgram("go\n", cps);
6942 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6943 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6944 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6945 SendToProgram("go\n", cps);
6946 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6948 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6952 ChessProgramState *savedState;
6953 void DeferredBookMove(void)
6955 if(savedState->lastPing != savedState->lastPong)
6956 ScheduleDelayedEvent(DeferredBookMove, 10);
6958 HandleMachineMove(savedMessage, savedState);
6962 HandleMachineMove(message, cps)
6964 ChessProgramState *cps;
6966 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6967 char realname[MSG_SIZ];
6968 int fromX, fromY, toX, toY;
6977 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6979 * Kludge to ignore BEL characters
6981 while (*message == '\007') message++;
6984 * [HGM] engine debug message: ignore lines starting with '#' character
6986 if(cps->debug && *message == '#') return;
6989 * Look for book output
6991 if (cps == &first && bookRequested) {
6992 if (message[0] == '\t' || message[0] == ' ') {
6993 /* Part of the book output is here; append it */
6994 strcat(bookOutput, message);
6995 strcat(bookOutput, " \n");
6997 } else if (bookOutput[0] != NULLCHAR) {
6998 /* All of book output has arrived; display it */
6999 char *p = bookOutput;
7000 while (*p != NULLCHAR) {
7001 if (*p == '\t') *p = ' ';
7004 DisplayInformation(bookOutput);
7005 bookRequested = FALSE;
7006 /* Fall through to parse the current output */
7011 * Look for machine move.
7013 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7014 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7016 /* This method is only useful on engines that support ping */
7017 if (cps->lastPing != cps->lastPong) {
7018 if (gameMode == BeginningOfGame) {
7019 /* Extra move from before last new; ignore */
7020 if (appData.debugMode) {
7021 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7024 if (appData.debugMode) {
7025 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7026 cps->which, gameMode);
7029 SendToProgram("undo\n", cps);
7035 case BeginningOfGame:
7036 /* Extra move from before last reset; ignore */
7037 if (appData.debugMode) {
7038 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7045 /* Extra move after we tried to stop. The mode test is
7046 not a reliable way of detecting this problem, but it's
7047 the best we can do on engines that don't support ping.
7049 if (appData.debugMode) {
7050 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7051 cps->which, gameMode);
7053 SendToProgram("undo\n", cps);
7056 case MachinePlaysWhite:
7057 case IcsPlayingWhite:
7058 machineWhite = TRUE;
7061 case MachinePlaysBlack:
7062 case IcsPlayingBlack:
7063 machineWhite = FALSE;
7066 case TwoMachinesPlay:
7067 machineWhite = (cps->twoMachinesColor[0] == 'w');
7070 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7071 if (appData.debugMode) {
7073 "Ignoring move out of turn by %s, gameMode %d"
7074 ", forwardMost %d\n",
7075 cps->which, gameMode, forwardMostMove);
7080 if (appData.debugMode) { int f = forwardMostMove;
7081 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7082 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7083 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7085 if(cps->alphaRank) AlphaRank(machineMove, 4);
7086 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7087 &fromX, &fromY, &toX, &toY, &promoChar)) {
7088 /* Machine move could not be parsed; ignore it. */
7089 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7090 machineMove, cps->which);
7091 DisplayError(buf1, 0);
7092 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7093 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7094 if (gameMode == TwoMachinesPlay) {
7095 GameEnds(machineWhite ? BlackWins : WhiteWins,
7101 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7102 /* So we have to redo legality test with true e.p. status here, */
7103 /* to make sure an illegal e.p. capture does not slip through, */
7104 /* to cause a forfeit on a justified illegal-move complaint */
7105 /* of the opponent. */
7106 if( gameMode==TwoMachinesPlay && appData.testLegality
7107 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7110 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7111 fromY, fromX, toY, toX, promoChar);
7112 if (appData.debugMode) {
7114 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7115 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7116 fprintf(debugFP, "castling rights\n");
7118 if(moveType == IllegalMove) {
7119 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7120 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7121 GameEnds(machineWhite ? BlackWins : WhiteWins,
7124 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7125 /* [HGM] Kludge to handle engines that send FRC-style castling
7126 when they shouldn't (like TSCP-Gothic) */
7128 case WhiteASideCastleFR:
7129 case BlackASideCastleFR:
7131 currentMoveString[2]++;
7133 case WhiteHSideCastleFR:
7134 case BlackHSideCastleFR:
7136 currentMoveString[2]--;
7138 default: ; // nothing to do, but suppresses warning of pedantic compilers
7141 hintRequested = FALSE;
7142 lastHint[0] = NULLCHAR;
7143 bookRequested = FALSE;
7144 /* Program may be pondering now */
7145 cps->maybeThinking = TRUE;
7146 if (cps->sendTime == 2) cps->sendTime = 1;
7147 if (cps->offeredDraw) cps->offeredDraw--;
7149 /* currentMoveString is set as a side-effect of ParseOneMove */
7150 strcpy(machineMove, currentMoveString);
7151 strcat(machineMove, "\n");
7152 strcpy(moveList[forwardMostMove], machineMove);
7154 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7156 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7157 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7160 while( count < adjudicateLossPlies ) {
7161 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7164 score = -score; /* Flip score for winning side */
7167 if( score > adjudicateLossThreshold ) {
7174 if( count >= adjudicateLossPlies ) {
7175 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7177 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7178 "Xboard adjudication",
7185 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7188 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7190 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7191 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7193 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7195 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7197 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7198 char buf[3*MSG_SIZ];
7200 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7201 programStats.score / 100.,
7203 programStats.time / 100.,
7204 (unsigned int)programStats.nodes,
7205 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7206 programStats.movelist);
7208 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7213 /* [AS] Save move info and clear stats for next move */
7214 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7215 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7216 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7217 ClearProgramStats();
7218 thinkOutput[0] = NULLCHAR;
7219 hiddenThinkOutputState = 0;
7222 if (gameMode == TwoMachinesPlay) {
7223 /* [HGM] relaying draw offers moved to after reception of move */
7224 /* and interpreting offer as claim if it brings draw condition */
7225 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7226 SendToProgram("draw\n", cps->other);
7228 if (cps->other->sendTime) {
7229 SendTimeRemaining(cps->other,
7230 cps->other->twoMachinesColor[0] == 'w');
7232 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7233 if (firstMove && !bookHit) {
7235 if (cps->other->useColors) {
7236 SendToProgram(cps->other->twoMachinesColor, cps->other);
7238 SendToProgram("go\n", cps->other);
7240 cps->other->maybeThinking = TRUE;
7243 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7245 if (!pausing && appData.ringBellAfterMoves) {
7250 * Reenable menu items that were disabled while
7251 * machine was thinking
7253 if (gameMode != TwoMachinesPlay)
7254 SetUserThinkingEnables();
7256 // [HGM] book: after book hit opponent has received move and is now in force mode
7257 // force the book reply into it, and then fake that it outputted this move by jumping
7258 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7260 static char bookMove[MSG_SIZ]; // a bit generous?
7262 strcpy(bookMove, "move ");
7263 strcat(bookMove, bookHit);
7266 programStats.nodes = programStats.depth = programStats.time =
7267 programStats.score = programStats.got_only_move = 0;
7268 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7270 if(cps->lastPing != cps->lastPong) {
7271 savedMessage = message; // args for deferred call
7273 ScheduleDelayedEvent(DeferredBookMove, 10);
7282 /* Set special modes for chess engines. Later something general
7283 * could be added here; for now there is just one kludge feature,
7284 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7285 * when "xboard" is given as an interactive command.
7287 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7288 cps->useSigint = FALSE;
7289 cps->useSigterm = FALSE;
7291 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7292 ParseFeatures(message+8, cps);
7293 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7296 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7297 * want this, I was asked to put it in, and obliged.
7299 if (!strncmp(message, "setboard ", 9)) {
7300 Board initial_position;
7302 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7304 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7305 DisplayError(_("Bad FEN received from engine"), 0);
7309 CopyBoard(boards[0], initial_position);
7310 initialRulePlies = FENrulePlies;
7311 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7312 else gameMode = MachinePlaysBlack;
7313 DrawPosition(FALSE, boards[currentMove]);
7319 * Look for communication commands
7321 if (!strncmp(message, "telluser ", 9)) {
7322 DisplayNote(message + 9);
7325 if (!strncmp(message, "tellusererror ", 14)) {
7327 DisplayError(message + 14, 0);
7330 if (!strncmp(message, "tellopponent ", 13)) {
7331 if (appData.icsActive) {
7333 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7337 DisplayNote(message + 13);
7341 if (!strncmp(message, "tellothers ", 11)) {
7342 if (appData.icsActive) {
7344 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7350 if (!strncmp(message, "tellall ", 8)) {
7351 if (appData.icsActive) {
7353 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7357 DisplayNote(message + 8);
7361 if (strncmp(message, "warning", 7) == 0) {
7362 /* Undocumented feature, use tellusererror in new code */
7363 DisplayError(message, 0);
7366 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7367 strcpy(realname, cps->tidy);
7368 strcat(realname, " query");
7369 AskQuestion(realname, buf2, buf1, cps->pr);
7372 /* Commands from the engine directly to ICS. We don't allow these to be
7373 * sent until we are logged on. Crafty kibitzes have been known to
7374 * interfere with the login process.
7377 if (!strncmp(message, "tellics ", 8)) {
7378 SendToICS(message + 8);
7382 if (!strncmp(message, "tellicsnoalias ", 15)) {
7383 SendToICS(ics_prefix);
7384 SendToICS(message + 15);
7388 /* The following are for backward compatibility only */
7389 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7390 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7391 SendToICS(ics_prefix);
7397 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7401 * If the move is illegal, cancel it and redraw the board.
7402 * Also deal with other error cases. Matching is rather loose
7403 * here to accommodate engines written before the spec.
7405 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7406 strncmp(message, "Error", 5) == 0) {
7407 if (StrStr(message, "name") ||
7408 StrStr(message, "rating") || StrStr(message, "?") ||
7409 StrStr(message, "result") || StrStr(message, "board") ||
7410 StrStr(message, "bk") || StrStr(message, "computer") ||
7411 StrStr(message, "variant") || StrStr(message, "hint") ||
7412 StrStr(message, "random") || StrStr(message, "depth") ||
7413 StrStr(message, "accepted")) {
7416 if (StrStr(message, "protover")) {
7417 /* Program is responding to input, so it's apparently done
7418 initializing, and this error message indicates it is
7419 protocol version 1. So we don't need to wait any longer
7420 for it to initialize and send feature commands. */
7421 FeatureDone(cps, 1);
7422 cps->protocolVersion = 1;
7425 cps->maybeThinking = FALSE;
7427 if (StrStr(message, "draw")) {
7428 /* Program doesn't have "draw" command */
7429 cps->sendDrawOffers = 0;
7432 if (cps->sendTime != 1 &&
7433 (StrStr(message, "time") || StrStr(message, "otim"))) {
7434 /* Program apparently doesn't have "time" or "otim" command */
7438 if (StrStr(message, "analyze")) {
7439 cps->analysisSupport = FALSE;
7440 cps->analyzing = FALSE;
7442 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7443 DisplayError(buf2, 0);
7446 if (StrStr(message, "(no matching move)st")) {
7447 /* Special kludge for GNU Chess 4 only */
7448 cps->stKludge = TRUE;
7449 SendTimeControl(cps, movesPerSession, timeControl,
7450 timeIncrement, appData.searchDepth,
7454 if (StrStr(message, "(no matching move)sd")) {
7455 /* Special kludge for GNU Chess 4 only */
7456 cps->sdKludge = TRUE;
7457 SendTimeControl(cps, movesPerSession, timeControl,
7458 timeIncrement, appData.searchDepth,
7462 if (!StrStr(message, "llegal")) {
7465 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7466 gameMode == IcsIdle) return;
7467 if (forwardMostMove <= backwardMostMove) return;
7468 if (pausing) PauseEvent();
7469 if(appData.forceIllegal) {
7470 // [HGM] illegal: machine refused move; force position after move into it
7471 SendToProgram("force\n", cps);
7472 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7473 // we have a real problem now, as SendBoard will use the a2a3 kludge
7474 // when black is to move, while there might be nothing on a2 or black
7475 // might already have the move. So send the board as if white has the move.
7476 // But first we must change the stm of the engine, as it refused the last move
7477 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7478 if(WhiteOnMove(forwardMostMove)) {
7479 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7480 SendBoard(cps, forwardMostMove); // kludgeless board
7482 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7483 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7484 SendBoard(cps, forwardMostMove+1); // kludgeless board
7486 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7487 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7488 gameMode == TwoMachinesPlay)
7489 SendToProgram("go\n", cps);
7492 if (gameMode == PlayFromGameFile) {
7493 /* Stop reading this game file */
7494 gameMode = EditGame;
7497 currentMove = forwardMostMove-1;
7498 DisplayMove(currentMove-1); /* before DisplayMoveError */
7499 SwitchClocks(forwardMostMove-1); // [HGM] race
7500 DisplayBothClocks();
7501 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7502 parseList[currentMove], cps->which);
7503 DisplayMoveError(buf1);
7504 DrawPosition(FALSE, boards[currentMove]);
7506 /* [HGM] illegal-move claim should forfeit game when Xboard */
7507 /* only passes fully legal moves */
7508 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7509 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7510 "False illegal-move claim", GE_XBOARD );
7514 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7515 /* Program has a broken "time" command that
7516 outputs a string not ending in newline.
7522 * If chess program startup fails, exit with an error message.
7523 * Attempts to recover here are futile.
7525 if ((StrStr(message, "unknown host") != NULL)
7526 || (StrStr(message, "No remote directory") != NULL)
7527 || (StrStr(message, "not found") != NULL)
7528 || (StrStr(message, "No such file") != NULL)
7529 || (StrStr(message, "can't alloc") != NULL)
7530 || (StrStr(message, "Permission denied") != NULL)) {
7532 cps->maybeThinking = FALSE;
7533 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7534 cps->which, cps->program, cps->host, message);
7535 RemoveInputSource(cps->isr);
7536 DisplayFatalError(buf1, 0, 1);
7541 * Look for hint output
7543 if (sscanf(message, "Hint: %s", buf1) == 1) {
7544 if (cps == &first && hintRequested) {
7545 hintRequested = FALSE;
7546 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7547 &fromX, &fromY, &toX, &toY, &promoChar)) {
7548 (void) CoordsToAlgebraic(boards[forwardMostMove],
7549 PosFlags(forwardMostMove),
7550 fromY, fromX, toY, toX, promoChar, buf1);
7551 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7552 DisplayInformation(buf2);
7554 /* Hint move could not be parsed!? */
7555 snprintf(buf2, sizeof(buf2),
7556 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7558 DisplayError(buf2, 0);
7561 strcpy(lastHint, buf1);
7567 * Ignore other messages if game is not in progress
7569 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7570 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7573 * look for win, lose, draw, or draw offer
7575 if (strncmp(message, "1-0", 3) == 0) {
7576 char *p, *q, *r = "";
7577 p = strchr(message, '{');
7585 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7587 } else if (strncmp(message, "0-1", 3) == 0) {
7588 char *p, *q, *r = "";
7589 p = strchr(message, '{');
7597 /* Kludge for Arasan 4.1 bug */
7598 if (strcmp(r, "Black resigns") == 0) {
7599 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7602 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7604 } else if (strncmp(message, "1/2", 3) == 0) {
7605 char *p, *q, *r = "";
7606 p = strchr(message, '{');
7615 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7618 } else if (strncmp(message, "White resign", 12) == 0) {
7619 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7621 } else if (strncmp(message, "Black resign", 12) == 0) {
7622 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7624 } else if (strncmp(message, "White matches", 13) == 0 ||
7625 strncmp(message, "Black matches", 13) == 0 ) {
7626 /* [HGM] ignore GNUShogi noises */
7628 } else if (strncmp(message, "White", 5) == 0 &&
7629 message[5] != '(' &&
7630 StrStr(message, "Black") == NULL) {
7631 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7633 } else if (strncmp(message, "Black", 5) == 0 &&
7634 message[5] != '(') {
7635 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7637 } else if (strcmp(message, "resign") == 0 ||
7638 strcmp(message, "computer resigns") == 0) {
7640 case MachinePlaysBlack:
7641 case IcsPlayingBlack:
7642 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7644 case MachinePlaysWhite:
7645 case IcsPlayingWhite:
7646 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7648 case TwoMachinesPlay:
7649 if (cps->twoMachinesColor[0] == 'w')
7650 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7652 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7659 } else if (strncmp(message, "opponent mates", 14) == 0) {
7661 case MachinePlaysBlack:
7662 case IcsPlayingBlack:
7663 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7665 case MachinePlaysWhite:
7666 case IcsPlayingWhite:
7667 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7669 case TwoMachinesPlay:
7670 if (cps->twoMachinesColor[0] == 'w')
7671 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7673 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7680 } else if (strncmp(message, "computer mates", 14) == 0) {
7682 case MachinePlaysBlack:
7683 case IcsPlayingBlack:
7684 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7686 case MachinePlaysWhite:
7687 case IcsPlayingWhite:
7688 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7690 case TwoMachinesPlay:
7691 if (cps->twoMachinesColor[0] == 'w')
7692 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7694 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7701 } else if (strncmp(message, "checkmate", 9) == 0) {
7702 if (WhiteOnMove(forwardMostMove)) {
7703 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7705 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7708 } else if (strstr(message, "Draw") != NULL ||
7709 strstr(message, "game is a draw") != NULL) {
7710 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7712 } else if (strstr(message, "offer") != NULL &&
7713 strstr(message, "draw") != NULL) {
7715 if (appData.zippyPlay && first.initDone) {
7716 /* Relay offer to ICS */
7717 SendToICS(ics_prefix);
7718 SendToICS("draw\n");
7721 cps->offeredDraw = 2; /* valid until this engine moves twice */
7722 if (gameMode == TwoMachinesPlay) {
7723 if (cps->other->offeredDraw) {
7724 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7725 /* [HGM] in two-machine mode we delay relaying draw offer */
7726 /* until after we also have move, to see if it is really claim */
7728 } else if (gameMode == MachinePlaysWhite ||
7729 gameMode == MachinePlaysBlack) {
7730 if (userOfferedDraw) {
7731 DisplayInformation(_("Machine accepts your draw offer"));
7732 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7734 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7741 * Look for thinking output
7743 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7744 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7746 int plylev, mvleft, mvtot, curscore, time;
7747 char mvname[MOVE_LEN];
7751 int prefixHint = FALSE;
7752 mvname[0] = NULLCHAR;
7755 case MachinePlaysBlack:
7756 case IcsPlayingBlack:
7757 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7759 case MachinePlaysWhite:
7760 case IcsPlayingWhite:
7761 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7766 case IcsObserving: /* [DM] icsEngineAnalyze */
7767 if (!appData.icsEngineAnalyze) ignore = TRUE;
7769 case TwoMachinesPlay:
7770 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7781 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7782 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7784 if (plyext != ' ' && plyext != '\t') {
7788 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7789 if( cps->scoreIsAbsolute &&
7790 ( gameMode == MachinePlaysBlack ||
7791 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7792 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7793 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7794 !WhiteOnMove(currentMove)
7797 curscore = -curscore;
7801 programStats.depth = plylev;
7802 programStats.nodes = nodes;
7803 programStats.time = time;
7804 programStats.score = curscore;
7805 programStats.got_only_move = 0;
7807 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7810 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7811 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7812 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7813 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7814 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7815 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7816 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7817 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7820 /* Buffer overflow protection */
7821 if (buf1[0] != NULLCHAR) {
7822 if (strlen(buf1) >= sizeof(programStats.movelist)
7823 && appData.debugMode) {
7825 "PV is too long; using the first %u bytes.\n",
7826 (unsigned) sizeof(programStats.movelist) - 1);
7829 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7831 sprintf(programStats.movelist, " no PV\n");
7834 if (programStats.seen_stat) {
7835 programStats.ok_to_send = 1;
7838 if (strchr(programStats.movelist, '(') != NULL) {
7839 programStats.line_is_book = 1;
7840 programStats.nr_moves = 0;
7841 programStats.moves_left = 0;
7843 programStats.line_is_book = 0;
7846 SendProgramStatsToFrontend( cps, &programStats );
7849 [AS] Protect the thinkOutput buffer from overflow... this
7850 is only useful if buf1 hasn't overflowed first!
7852 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7854 (gameMode == TwoMachinesPlay ?
7855 ToUpper(cps->twoMachinesColor[0]) : ' '),
7856 ((double) curscore) / 100.0,
7857 prefixHint ? lastHint : "",
7858 prefixHint ? " " : "" );
7860 if( buf1[0] != NULLCHAR ) {
7861 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7863 if( strlen(buf1) > max_len ) {
7864 if( appData.debugMode) {
7865 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7867 buf1[max_len+1] = '\0';
7870 strcat( thinkOutput, buf1 );
7873 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7874 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7875 DisplayMove(currentMove - 1);
7879 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7880 /* crafty (9.25+) says "(only move) <move>"
7881 * if there is only 1 legal move
7883 sscanf(p, "(only move) %s", buf1);
7884 sprintf(thinkOutput, "%s (only move)", buf1);
7885 sprintf(programStats.movelist, "%s (only move)", buf1);
7886 programStats.depth = 1;
7887 programStats.nr_moves = 1;
7888 programStats.moves_left = 1;
7889 programStats.nodes = 1;
7890 programStats.time = 1;
7891 programStats.got_only_move = 1;
7893 /* Not really, but we also use this member to
7894 mean "line isn't going to change" (Crafty
7895 isn't searching, so stats won't change) */
7896 programStats.line_is_book = 1;
7898 SendProgramStatsToFrontend( cps, &programStats );
7900 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7901 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7902 DisplayMove(currentMove - 1);
7905 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7906 &time, &nodes, &plylev, &mvleft,
7907 &mvtot, mvname) >= 5) {
7908 /* The stat01: line is from Crafty (9.29+) in response
7909 to the "." command */
7910 programStats.seen_stat = 1;
7911 cps->maybeThinking = TRUE;
7913 if (programStats.got_only_move || !appData.periodicUpdates)
7916 programStats.depth = plylev;
7917 programStats.time = time;
7918 programStats.nodes = nodes;
7919 programStats.moves_left = mvleft;
7920 programStats.nr_moves = mvtot;
7921 strcpy(programStats.move_name, mvname);
7922 programStats.ok_to_send = 1;
7923 programStats.movelist[0] = '\0';
7925 SendProgramStatsToFrontend( cps, &programStats );
7929 } else if (strncmp(message,"++",2) == 0) {
7930 /* Crafty 9.29+ outputs this */
7931 programStats.got_fail = 2;
7934 } else if (strncmp(message,"--",2) == 0) {
7935 /* Crafty 9.29+ outputs this */
7936 programStats.got_fail = 1;
7939 } else if (thinkOutput[0] != NULLCHAR &&
7940 strncmp(message, " ", 4) == 0) {
7941 unsigned message_len;
7944 while (*p && *p == ' ') p++;
7946 message_len = strlen( p );
7948 /* [AS] Avoid buffer overflow */
7949 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7950 strcat(thinkOutput, " ");
7951 strcat(thinkOutput, p);
7954 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7955 strcat(programStats.movelist, " ");
7956 strcat(programStats.movelist, p);
7959 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7960 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7961 DisplayMove(currentMove - 1);
7969 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7970 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7972 ChessProgramStats cpstats;
7974 if (plyext != ' ' && plyext != '\t') {
7978 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7979 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7980 curscore = -curscore;
7983 cpstats.depth = plylev;
7984 cpstats.nodes = nodes;
7985 cpstats.time = time;
7986 cpstats.score = curscore;
7987 cpstats.got_only_move = 0;
7988 cpstats.movelist[0] = '\0';
7990 if (buf1[0] != NULLCHAR) {
7991 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7994 cpstats.ok_to_send = 0;
7995 cpstats.line_is_book = 0;
7996 cpstats.nr_moves = 0;
7997 cpstats.moves_left = 0;
7999 SendProgramStatsToFrontend( cps, &cpstats );
8006 /* Parse a game score from the character string "game", and
8007 record it as the history of the current game. The game
8008 score is NOT assumed to start from the standard position.
8009 The display is not updated in any way.
8012 ParseGameHistory(game)
8016 int fromX, fromY, toX, toY, boardIndex;
8021 if (appData.debugMode)
8022 fprintf(debugFP, "Parsing game history: %s\n", game);
8024 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8025 gameInfo.site = StrSave(appData.icsHost);
8026 gameInfo.date = PGNDate();
8027 gameInfo.round = StrSave("-");
8029 /* Parse out names of players */
8030 while (*game == ' ') game++;
8032 while (*game != ' ') *p++ = *game++;
8034 gameInfo.white = StrSave(buf);
8035 while (*game == ' ') game++;
8037 while (*game != ' ' && *game != '\n') *p++ = *game++;
8039 gameInfo.black = StrSave(buf);
8042 boardIndex = blackPlaysFirst ? 1 : 0;
8045 yyboardindex = boardIndex;
8046 moveType = (ChessMove) yylex();
8048 case IllegalMove: /* maybe suicide chess, etc. */
8049 if (appData.debugMode) {
8050 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8051 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8052 setbuf(debugFP, NULL);
8054 case WhitePromotionChancellor:
8055 case BlackPromotionChancellor:
8056 case WhitePromotionArchbishop:
8057 case BlackPromotionArchbishop:
8058 case WhitePromotionQueen:
8059 case BlackPromotionQueen:
8060 case WhitePromotionRook:
8061 case BlackPromotionRook:
8062 case WhitePromotionBishop:
8063 case BlackPromotionBishop:
8064 case WhitePromotionKnight:
8065 case BlackPromotionKnight:
8066 case WhitePromotionKing:
8067 case BlackPromotionKing:
8069 case WhiteCapturesEnPassant:
8070 case BlackCapturesEnPassant:
8071 case WhiteKingSideCastle:
8072 case WhiteQueenSideCastle:
8073 case BlackKingSideCastle:
8074 case BlackQueenSideCastle:
8075 case WhiteKingSideCastleWild:
8076 case WhiteQueenSideCastleWild:
8077 case BlackKingSideCastleWild:
8078 case BlackQueenSideCastleWild:
8080 case WhiteHSideCastleFR:
8081 case WhiteASideCastleFR:
8082 case BlackHSideCastleFR:
8083 case BlackASideCastleFR:
8085 fromX = currentMoveString[0] - AAA;
8086 fromY = currentMoveString[1] - ONE;
8087 toX = currentMoveString[2] - AAA;
8088 toY = currentMoveString[3] - ONE;
8089 promoChar = currentMoveString[4];
8093 fromX = moveType == WhiteDrop ?
8094 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8095 (int) CharToPiece(ToLower(currentMoveString[0]));
8097 toX = currentMoveString[2] - AAA;
8098 toY = currentMoveString[3] - ONE;
8099 promoChar = NULLCHAR;
8103 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8104 if (appData.debugMode) {
8105 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8106 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8107 setbuf(debugFP, NULL);
8109 DisplayError(buf, 0);
8111 case ImpossibleMove:
8113 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8114 if (appData.debugMode) {
8115 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8116 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8117 setbuf(debugFP, NULL);
8119 DisplayError(buf, 0);
8121 case (ChessMove) 0: /* end of file */
8122 if (boardIndex < backwardMostMove) {
8123 /* Oops, gap. How did that happen? */
8124 DisplayError(_("Gap in move list"), 0);
8127 backwardMostMove = blackPlaysFirst ? 1 : 0;
8128 if (boardIndex > forwardMostMove) {
8129 forwardMostMove = boardIndex;
8133 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8134 strcat(parseList[boardIndex-1], " ");
8135 strcat(parseList[boardIndex-1], yy_text);
8147 case GameUnfinished:
8148 if (gameMode == IcsExamining) {
8149 if (boardIndex < backwardMostMove) {
8150 /* Oops, gap. How did that happen? */
8153 backwardMostMove = blackPlaysFirst ? 1 : 0;
8156 gameInfo.result = moveType;
8157 p = strchr(yy_text, '{');
8158 if (p == NULL) p = strchr(yy_text, '(');
8161 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8163 q = strchr(p, *p == '{' ? '}' : ')');
8164 if (q != NULL) *q = NULLCHAR;
8167 gameInfo.resultDetails = StrSave(p);
8170 if (boardIndex >= forwardMostMove &&
8171 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8172 backwardMostMove = blackPlaysFirst ? 1 : 0;
8175 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8176 fromY, fromX, toY, toX, promoChar,
8177 parseList[boardIndex]);
8178 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8179 /* currentMoveString is set as a side-effect of yylex */
8180 strcpy(moveList[boardIndex], currentMoveString);
8181 strcat(moveList[boardIndex], "\n");
8183 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8184 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8190 if(gameInfo.variant != VariantShogi)
8191 strcat(parseList[boardIndex - 1], "+");
8195 strcat(parseList[boardIndex - 1], "#");
8202 /* Apply a move to the given board */
8204 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8205 int fromX, fromY, toX, toY;
8209 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8210 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8212 /* [HGM] compute & store e.p. status and castling rights for new position */
8213 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8216 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8217 oldEP = (signed char)board[EP_STATUS];
8218 board[EP_STATUS] = EP_NONE;
8220 if( board[toY][toX] != EmptySquare )
8221 board[EP_STATUS] = EP_CAPTURE;
8223 if( board[fromY][fromX] == WhitePawn ) {
8224 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8225 board[EP_STATUS] = EP_PAWN_MOVE;
8227 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8228 gameInfo.variant != VariantBerolina || toX < fromX)
8229 board[EP_STATUS] = toX | berolina;
8230 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8231 gameInfo.variant != VariantBerolina || toX > fromX)
8232 board[EP_STATUS] = toX;
8235 if( board[fromY][fromX] == BlackPawn ) {
8236 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8237 board[EP_STATUS] = EP_PAWN_MOVE;
8238 if( toY-fromY== -2) {
8239 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8240 gameInfo.variant != VariantBerolina || toX < fromX)
8241 board[EP_STATUS] = toX | berolina;
8242 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8243 gameInfo.variant != VariantBerolina || toX > fromX)
8244 board[EP_STATUS] = toX;
8248 for(i=0; i<nrCastlingRights; i++) {
8249 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8250 board[CASTLING][i] == toX && castlingRank[i] == toY
8251 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8256 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8257 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8258 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8260 if (fromX == toX && fromY == toY) return;
8262 if (fromY == DROP_RANK) {
8264 piece = board[toY][toX] = (ChessSquare) fromX;
8266 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8267 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8268 if(gameInfo.variant == VariantKnightmate)
8269 king += (int) WhiteUnicorn - (int) WhiteKing;
8271 /* Code added by Tord: */
8272 /* FRC castling assumed when king captures friendly rook. */
8273 if (board[fromY][fromX] == WhiteKing &&
8274 board[toY][toX] == WhiteRook) {
8275 board[fromY][fromX] = EmptySquare;
8276 board[toY][toX] = EmptySquare;
8278 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8280 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8282 } else if (board[fromY][fromX] == BlackKing &&
8283 board[toY][toX] == BlackRook) {
8284 board[fromY][fromX] = EmptySquare;
8285 board[toY][toX] = EmptySquare;
8287 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8289 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8291 /* End of code added by Tord */
8293 } else if (board[fromY][fromX] == king
8294 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8295 && toY == fromY && toX > fromX+1) {
8296 board[fromY][fromX] = EmptySquare;
8297 board[toY][toX] = king;
8298 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8299 board[fromY][BOARD_RGHT-1] = EmptySquare;
8300 } else if (board[fromY][fromX] == king
8301 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8302 && toY == fromY && toX < fromX-1) {
8303 board[fromY][fromX] = EmptySquare;
8304 board[toY][toX] = king;
8305 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8306 board[fromY][BOARD_LEFT] = EmptySquare;
8307 } else if (board[fromY][fromX] == WhitePawn
8308 && toY >= BOARD_HEIGHT-promoRank
8309 && gameInfo.variant != VariantXiangqi
8311 /* white pawn promotion */
8312 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8313 if (board[toY][toX] == EmptySquare) {
8314 board[toY][toX] = WhiteQueen;
8316 if(gameInfo.variant==VariantBughouse ||
8317 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8318 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8319 board[fromY][fromX] = EmptySquare;
8320 } else if ((fromY == BOARD_HEIGHT-4)
8322 && gameInfo.variant != VariantXiangqi
8323 && gameInfo.variant != VariantBerolina
8324 && (board[fromY][fromX] == WhitePawn)
8325 && (board[toY][toX] == EmptySquare)) {
8326 board[fromY][fromX] = EmptySquare;
8327 board[toY][toX] = WhitePawn;
8328 captured = board[toY - 1][toX];
8329 board[toY - 1][toX] = EmptySquare;
8330 } else if ((fromY == BOARD_HEIGHT-4)
8332 && gameInfo.variant == VariantBerolina
8333 && (board[fromY][fromX] == WhitePawn)
8334 && (board[toY][toX] == EmptySquare)) {
8335 board[fromY][fromX] = EmptySquare;
8336 board[toY][toX] = WhitePawn;
8337 if(oldEP & EP_BEROLIN_A) {
8338 captured = board[fromY][fromX-1];
8339 board[fromY][fromX-1] = EmptySquare;
8340 }else{ captured = board[fromY][fromX+1];
8341 board[fromY][fromX+1] = EmptySquare;
8343 } else if (board[fromY][fromX] == king
8344 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8345 && toY == fromY && toX > fromX+1) {
8346 board[fromY][fromX] = EmptySquare;
8347 board[toY][toX] = king;
8348 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8349 board[fromY][BOARD_RGHT-1] = EmptySquare;
8350 } else if (board[fromY][fromX] == king
8351 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8352 && toY == fromY && toX < fromX-1) {
8353 board[fromY][fromX] = EmptySquare;
8354 board[toY][toX] = king;
8355 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8356 board[fromY][BOARD_LEFT] = EmptySquare;
8357 } else if (fromY == 7 && fromX == 3
8358 && board[fromY][fromX] == BlackKing
8359 && toY == 7 && toX == 5) {
8360 board[fromY][fromX] = EmptySquare;
8361 board[toY][toX] = BlackKing;
8362 board[fromY][7] = EmptySquare;
8363 board[toY][4] = BlackRook;
8364 } else if (fromY == 7 && fromX == 3
8365 && board[fromY][fromX] == BlackKing
8366 && toY == 7 && toX == 1) {
8367 board[fromY][fromX] = EmptySquare;
8368 board[toY][toX] = BlackKing;
8369 board[fromY][0] = EmptySquare;
8370 board[toY][2] = BlackRook;
8371 } else if (board[fromY][fromX] == BlackPawn
8373 && gameInfo.variant != VariantXiangqi
8375 /* black pawn promotion */
8376 board[toY][toX] = CharToPiece(ToLower(promoChar));
8377 if (board[toY][toX] == EmptySquare) {
8378 board[toY][toX] = BlackQueen;
8380 if(gameInfo.variant==VariantBughouse ||
8381 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8382 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8383 board[fromY][fromX] = EmptySquare;
8384 } else if ((fromY == 3)
8386 && gameInfo.variant != VariantXiangqi
8387 && gameInfo.variant != VariantBerolina
8388 && (board[fromY][fromX] == BlackPawn)
8389 && (board[toY][toX] == EmptySquare)) {
8390 board[fromY][fromX] = EmptySquare;
8391 board[toY][toX] = BlackPawn;
8392 captured = board[toY + 1][toX];
8393 board[toY + 1][toX] = EmptySquare;
8394 } else if ((fromY == 3)
8396 && gameInfo.variant == VariantBerolina
8397 && (board[fromY][fromX] == BlackPawn)
8398 && (board[toY][toX] == EmptySquare)) {
8399 board[fromY][fromX] = EmptySquare;
8400 board[toY][toX] = BlackPawn;
8401 if(oldEP & EP_BEROLIN_A) {
8402 captured = board[fromY][fromX-1];
8403 board[fromY][fromX-1] = EmptySquare;
8404 }else{ captured = board[fromY][fromX+1];
8405 board[fromY][fromX+1] = EmptySquare;
8408 board[toY][toX] = board[fromY][fromX];
8409 board[fromY][fromX] = EmptySquare;
8412 /* [HGM] now we promote for Shogi, if needed */
8413 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8414 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8417 if (gameInfo.holdingsWidth != 0) {
8419 /* !!A lot more code needs to be written to support holdings */
8420 /* [HGM] OK, so I have written it. Holdings are stored in the */
8421 /* penultimate board files, so they are automaticlly stored */
8422 /* in the game history. */
8423 if (fromY == DROP_RANK) {
8424 /* Delete from holdings, by decreasing count */
8425 /* and erasing image if necessary */
8427 if(p < (int) BlackPawn) { /* white drop */
8428 p -= (int)WhitePawn;
8429 p = PieceToNumber((ChessSquare)p);
8430 if(p >= gameInfo.holdingsSize) p = 0;
8431 if(--board[p][BOARD_WIDTH-2] <= 0)
8432 board[p][BOARD_WIDTH-1] = EmptySquare;
8433 if((int)board[p][BOARD_WIDTH-2] < 0)
8434 board[p][BOARD_WIDTH-2] = 0;
8435 } else { /* black drop */
8436 p -= (int)BlackPawn;
8437 p = PieceToNumber((ChessSquare)p);
8438 if(p >= gameInfo.holdingsSize) p = 0;
8439 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8440 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8441 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8442 board[BOARD_HEIGHT-1-p][1] = 0;
8445 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8446 && gameInfo.variant != VariantBughouse ) {
8447 /* [HGM] holdings: Add to holdings, if holdings exist */
8448 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8449 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8450 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8453 if (p >= (int) BlackPawn) {
8454 p -= (int)BlackPawn;
8455 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8456 /* in Shogi restore piece to its original first */
8457 captured = (ChessSquare) (DEMOTED captured);
8460 p = PieceToNumber((ChessSquare)p);
8461 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8462 board[p][BOARD_WIDTH-2]++;
8463 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8465 p -= (int)WhitePawn;
8466 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8467 captured = (ChessSquare) (DEMOTED captured);
8470 p = PieceToNumber((ChessSquare)p);
8471 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8472 board[BOARD_HEIGHT-1-p][1]++;
8473 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8476 } else if (gameInfo.variant == VariantAtomic) {
8477 if (captured != EmptySquare) {
8479 for (y = toY-1; y <= toY+1; y++) {
8480 for (x = toX-1; x <= toX+1; x++) {
8481 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8482 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8483 board[y][x] = EmptySquare;
8487 board[toY][toX] = EmptySquare;
8490 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8491 /* [HGM] Shogi promotions */
8492 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8495 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8496 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8497 // [HGM] superchess: take promotion piece out of holdings
8498 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8499 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8500 if(!--board[k][BOARD_WIDTH-2])
8501 board[k][BOARD_WIDTH-1] = EmptySquare;
8503 if(!--board[BOARD_HEIGHT-1-k][1])
8504 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8510 /* Updates forwardMostMove */
8512 MakeMove(fromX, fromY, toX, toY, promoChar)
8513 int fromX, fromY, toX, toY;
8516 // forwardMostMove++; // [HGM] bare: moved downstream
8518 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8519 int timeLeft; static int lastLoadFlag=0; int king, piece;
8520 piece = boards[forwardMostMove][fromY][fromX];
8521 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8522 if(gameInfo.variant == VariantKnightmate)
8523 king += (int) WhiteUnicorn - (int) WhiteKing;
8524 if(forwardMostMove == 0) {
8526 fprintf(serverMoves, "%s;", second.tidy);
8527 fprintf(serverMoves, "%s;", first.tidy);
8528 if(!blackPlaysFirst)
8529 fprintf(serverMoves, "%s;", second.tidy);
8530 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8531 lastLoadFlag = loadFlag;
8533 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8534 // print castling suffix
8535 if( toY == fromY && piece == king ) {
8537 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8539 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8542 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8543 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8544 boards[forwardMostMove][toY][toX] == EmptySquare
8546 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8548 if(promoChar != NULLCHAR)
8549 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8551 fprintf(serverMoves, "/%d/%d",
8552 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8553 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8554 else timeLeft = blackTimeRemaining/1000;
8555 fprintf(serverMoves, "/%d", timeLeft);
8557 fflush(serverMoves);
8560 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8561 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8565 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8566 if (commentList[forwardMostMove+1] != NULL) {
8567 free(commentList[forwardMostMove+1]);
8568 commentList[forwardMostMove+1] = NULL;
8570 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8571 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8572 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8573 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8574 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8575 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8576 gameInfo.result = GameUnfinished;
8577 if (gameInfo.resultDetails != NULL) {
8578 free(gameInfo.resultDetails);
8579 gameInfo.resultDetails = NULL;
8581 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8582 moveList[forwardMostMove - 1]);
8583 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8584 PosFlags(forwardMostMove - 1),
8585 fromY, fromX, toY, toX, promoChar,
8586 parseList[forwardMostMove - 1]);
8587 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8593 if(gameInfo.variant != VariantShogi)
8594 strcat(parseList[forwardMostMove - 1], "+");
8598 strcat(parseList[forwardMostMove - 1], "#");
8601 if (appData.debugMode) {
8602 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8607 /* Updates currentMove if not pausing */
8609 ShowMove(fromX, fromY, toX, toY)
8611 int instant = (gameMode == PlayFromGameFile) ?
8612 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8613 if(appData.noGUI) return;
8614 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8616 if (forwardMostMove == currentMove + 1) {
8617 AnimateMove(boards[forwardMostMove - 1],
8618 fromX, fromY, toX, toY);
8620 if (appData.highlightLastMove) {
8621 SetHighlights(fromX, fromY, toX, toY);
8624 currentMove = forwardMostMove;
8627 if (instant) return;
8629 DisplayMove(currentMove - 1);
8630 DrawPosition(FALSE, boards[currentMove]);
8631 DisplayBothClocks();
8632 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8635 void SendEgtPath(ChessProgramState *cps)
8636 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8637 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8639 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8642 char c, *q = name+1, *r, *s;
8644 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8645 while(*p && *p != ',') *q++ = *p++;
8647 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8648 strcmp(name, ",nalimov:") == 0 ) {
8649 // take nalimov path from the menu-changeable option first, if it is defined
8650 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8651 SendToProgram(buf,cps); // send egtbpath command for nalimov
8653 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8654 (s = StrStr(appData.egtFormats, name)) != NULL) {
8655 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8656 s = r = StrStr(s, ":") + 1; // beginning of path info
8657 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8658 c = *r; *r = 0; // temporarily null-terminate path info
8659 *--q = 0; // strip of trailig ':' from name
8660 sprintf(buf, "egtpath %s %s\n", name+1, s);
8662 SendToProgram(buf,cps); // send egtbpath command for this format
8664 if(*p == ',') p++; // read away comma to position for next format name
8669 InitChessProgram(cps, setup)
8670 ChessProgramState *cps;
8671 int setup; /* [HGM] needed to setup FRC opening position */
8673 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8674 if (appData.noChessProgram) return;
8675 hintRequested = FALSE;
8676 bookRequested = FALSE;
8678 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8679 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8680 if(cps->memSize) { /* [HGM] memory */
8681 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8682 SendToProgram(buf, cps);
8684 SendEgtPath(cps); /* [HGM] EGT */
8685 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8686 sprintf(buf, "cores %d\n", appData.smpCores);
8687 SendToProgram(buf, cps);
8690 SendToProgram(cps->initString, cps);
8691 if (gameInfo.variant != VariantNormal &&
8692 gameInfo.variant != VariantLoadable
8693 /* [HGM] also send variant if board size non-standard */
8694 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8696 char *v = VariantName(gameInfo.variant);
8697 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8698 /* [HGM] in protocol 1 we have to assume all variants valid */
8699 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8700 DisplayFatalError(buf, 0, 1);
8704 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8705 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8706 if( gameInfo.variant == VariantXiangqi )
8707 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8708 if( gameInfo.variant == VariantShogi )
8709 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8710 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8711 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8712 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8713 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8714 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8715 if( gameInfo.variant == VariantCourier )
8716 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8717 if( gameInfo.variant == VariantSuper )
8718 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8719 if( gameInfo.variant == VariantGreat )
8720 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8723 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8724 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8725 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8726 if(StrStr(cps->variants, b) == NULL) {
8727 // specific sized variant not known, check if general sizing allowed
8728 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8729 if(StrStr(cps->variants, "boardsize") == NULL) {
8730 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8731 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8732 DisplayFatalError(buf, 0, 1);
8735 /* [HGM] here we really should compare with the maximum supported board size */
8738 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8739 sprintf(buf, "variant %s\n", b);
8740 SendToProgram(buf, cps);
8742 currentlyInitializedVariant = gameInfo.variant;
8744 /* [HGM] send opening position in FRC to first engine */
8746 SendToProgram("force\n", cps);
8748 /* engine is now in force mode! Set flag to wake it up after first move. */
8749 setboardSpoiledMachineBlack = 1;
8753 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8754 SendToProgram(buf, cps);
8756 cps->maybeThinking = FALSE;
8757 cps->offeredDraw = 0;
8758 if (!appData.icsActive) {
8759 SendTimeControl(cps, movesPerSession, timeControl,
8760 timeIncrement, appData.searchDepth,
8763 if (appData.showThinking
8764 // [HGM] thinking: four options require thinking output to be sent
8765 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8767 SendToProgram("post\n", cps);
8769 SendToProgram("hard\n", cps);
8770 if (!appData.ponderNextMove) {
8771 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8772 it without being sure what state we are in first. "hard"
8773 is not a toggle, so that one is OK.
8775 SendToProgram("easy\n", cps);
8778 sprintf(buf, "ping %d\n", ++cps->lastPing);
8779 SendToProgram(buf, cps);
8781 cps->initDone = TRUE;
8786 StartChessProgram(cps)
8787 ChessProgramState *cps;
8792 if (appData.noChessProgram) return;
8793 cps->initDone = FALSE;
8795 if (strcmp(cps->host, "localhost") == 0) {
8796 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8797 } else if (*appData.remoteShell == NULLCHAR) {
8798 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8800 if (*appData.remoteUser == NULLCHAR) {
8801 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8804 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8805 cps->host, appData.remoteUser, cps->program);
8807 err = StartChildProcess(buf, "", &cps->pr);
8811 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8812 DisplayFatalError(buf, err, 1);
8818 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8819 if (cps->protocolVersion > 1) {
8820 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8821 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8822 cps->comboCnt = 0; // and values of combo boxes
8823 SendToProgram(buf, cps);
8825 SendToProgram("xboard\n", cps);
8831 TwoMachinesEventIfReady P((void))
8833 if (first.lastPing != first.lastPong) {
8834 DisplayMessage("", _("Waiting for first chess program"));
8835 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8838 if (second.lastPing != second.lastPong) {
8839 DisplayMessage("", _("Waiting for second chess program"));
8840 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8848 NextMatchGame P((void))
8850 int index; /* [HGM] autoinc: step load index during match */
8852 if (*appData.loadGameFile != NULLCHAR) {
8853 index = appData.loadGameIndex;
8854 if(index < 0) { // [HGM] autoinc
8855 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8856 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8858 LoadGameFromFile(appData.loadGameFile,
8860 appData.loadGameFile, FALSE);
8861 } else if (*appData.loadPositionFile != NULLCHAR) {
8862 index = appData.loadPositionIndex;
8863 if(index < 0) { // [HGM] autoinc
8864 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8865 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8867 LoadPositionFromFile(appData.loadPositionFile,
8869 appData.loadPositionFile);
8871 TwoMachinesEventIfReady();
8874 void UserAdjudicationEvent( int result )
8876 ChessMove gameResult = GameIsDrawn;
8879 gameResult = WhiteWins;
8881 else if( result < 0 ) {
8882 gameResult = BlackWins;
8885 if( gameMode == TwoMachinesPlay ) {
8886 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8891 // [HGM] save: calculate checksum of game to make games easily identifiable
8892 int StringCheckSum(char *s)
8895 if(s==NULL) return 0;
8896 while(*s) i = i*259 + *s++;
8903 for(i=backwardMostMove; i<forwardMostMove; i++) {
8904 sum += pvInfoList[i].depth;
8905 sum += StringCheckSum(parseList[i]);
8906 sum += StringCheckSum(commentList[i]);
8909 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8910 return sum + StringCheckSum(commentList[i]);
8911 } // end of save patch
8914 GameEnds(result, resultDetails, whosays)
8916 char *resultDetails;
8919 GameMode nextGameMode;
8923 if(endingGame) return; /* [HGM] crash: forbid recursion */
8925 if(twoBoards) { // [HGM] dual: switch back to one board
8926 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8927 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8929 if (appData.debugMode) {
8930 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8931 result, resultDetails ? resultDetails : "(null)", whosays);
8934 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8936 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8937 /* If we are playing on ICS, the server decides when the
8938 game is over, but the engine can offer to draw, claim
8942 if (appData.zippyPlay && first.initDone) {
8943 if (result == GameIsDrawn) {
8944 /* In case draw still needs to be claimed */
8945 SendToICS(ics_prefix);
8946 SendToICS("draw\n");
8947 } else if (StrCaseStr(resultDetails, "resign")) {
8948 SendToICS(ics_prefix);
8949 SendToICS("resign\n");
8953 endingGame = 0; /* [HGM] crash */
8957 /* If we're loading the game from a file, stop */
8958 if (whosays == GE_FILE) {
8959 (void) StopLoadGameTimer();
8963 /* Cancel draw offers */
8964 first.offeredDraw = second.offeredDraw = 0;
8966 /* If this is an ICS game, only ICS can really say it's done;
8967 if not, anyone can. */
8968 isIcsGame = (gameMode == IcsPlayingWhite ||
8969 gameMode == IcsPlayingBlack ||
8970 gameMode == IcsObserving ||
8971 gameMode == IcsExamining);
8973 if (!isIcsGame || whosays == GE_ICS) {
8974 /* OK -- not an ICS game, or ICS said it was done */
8976 if (!isIcsGame && !appData.noChessProgram)
8977 SetUserThinkingEnables();
8979 /* [HGM] if a machine claims the game end we verify this claim */
8980 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8981 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8983 ChessMove trueResult = (ChessMove) -1;
8985 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8986 first.twoMachinesColor[0] :
8987 second.twoMachinesColor[0] ;
8989 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8990 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8991 /* [HGM] verify: engine mate claims accepted if they were flagged */
8992 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8994 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8995 /* [HGM] verify: engine mate claims accepted if they were flagged */
8996 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8998 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8999 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9002 // now verify win claims, but not in drop games, as we don't understand those yet
9003 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9004 || gameInfo.variant == VariantGreat) &&
9005 (result == WhiteWins && claimer == 'w' ||
9006 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9007 if (appData.debugMode) {
9008 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9009 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9011 if(result != trueResult) {
9012 sprintf(buf, "False win claim: '%s'", resultDetails);
9013 result = claimer == 'w' ? BlackWins : WhiteWins;
9014 resultDetails = buf;
9017 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9018 && (forwardMostMove <= backwardMostMove ||
9019 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9020 (claimer=='b')==(forwardMostMove&1))
9022 /* [HGM] verify: draws that were not flagged are false claims */
9023 sprintf(buf, "False draw claim: '%s'", resultDetails);
9024 result = claimer == 'w' ? BlackWins : WhiteWins;
9025 resultDetails = buf;
9027 /* (Claiming a loss is accepted no questions asked!) */
9029 /* [HGM] bare: don't allow bare King to win */
9030 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9031 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9032 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9033 && result != GameIsDrawn)
9034 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9035 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9036 int p = (signed char)boards[forwardMostMove][i][j] - color;
9037 if(p >= 0 && p <= (int)WhiteKing) k++;
9039 if (appData.debugMode) {
9040 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9041 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9044 result = GameIsDrawn;
9045 sprintf(buf, "%s but bare king", resultDetails);
9046 resultDetails = buf;
9052 if(serverMoves != NULL && !loadFlag) { char c = '=';
9053 if(result==WhiteWins) c = '+';
9054 if(result==BlackWins) c = '-';
9055 if(resultDetails != NULL)
9056 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9058 if (resultDetails != NULL) {
9059 gameInfo.result = result;
9060 gameInfo.resultDetails = StrSave(resultDetails);
9062 /* display last move only if game was not loaded from file */
9063 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9064 DisplayMove(currentMove - 1);
9066 if (forwardMostMove != 0) {
9067 if (gameMode != PlayFromGameFile && gameMode != EditGame
9068 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9070 if (*appData.saveGameFile != NULLCHAR) {
9071 SaveGameToFile(appData.saveGameFile, TRUE);
9072 } else if (appData.autoSaveGames) {
9075 if (*appData.savePositionFile != NULLCHAR) {
9076 SavePositionToFile(appData.savePositionFile);
9081 /* Tell program how game ended in case it is learning */
9082 /* [HGM] Moved this to after saving the PGN, just in case */
9083 /* engine died and we got here through time loss. In that */
9084 /* case we will get a fatal error writing the pipe, which */
9085 /* would otherwise lose us the PGN. */
9086 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9087 /* output during GameEnds should never be fatal anymore */
9088 if (gameMode == MachinePlaysWhite ||
9089 gameMode == MachinePlaysBlack ||
9090 gameMode == TwoMachinesPlay ||
9091 gameMode == IcsPlayingWhite ||
9092 gameMode == IcsPlayingBlack ||
9093 gameMode == BeginningOfGame) {
9095 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9097 if (first.pr != NoProc) {
9098 SendToProgram(buf, &first);
9100 if (second.pr != NoProc &&
9101 gameMode == TwoMachinesPlay) {
9102 SendToProgram(buf, &second);
9107 if (appData.icsActive) {
9108 if (appData.quietPlay &&
9109 (gameMode == IcsPlayingWhite ||
9110 gameMode == IcsPlayingBlack)) {
9111 SendToICS(ics_prefix);
9112 SendToICS("set shout 1\n");
9114 nextGameMode = IcsIdle;
9115 ics_user_moved = FALSE;
9116 /* clean up premove. It's ugly when the game has ended and the
9117 * premove highlights are still on the board.
9121 ClearPremoveHighlights();
9122 DrawPosition(FALSE, boards[currentMove]);
9124 if (whosays == GE_ICS) {
9127 if (gameMode == IcsPlayingWhite)
9129 else if(gameMode == IcsPlayingBlack)
9133 if (gameMode == IcsPlayingBlack)
9135 else if(gameMode == IcsPlayingWhite)
9142 PlayIcsUnfinishedSound();
9145 } else if (gameMode == EditGame ||
9146 gameMode == PlayFromGameFile ||
9147 gameMode == AnalyzeMode ||
9148 gameMode == AnalyzeFile) {
9149 nextGameMode = gameMode;
9151 nextGameMode = EndOfGame;
9156 nextGameMode = gameMode;
9159 if (appData.noChessProgram) {
9160 gameMode = nextGameMode;
9162 endingGame = 0; /* [HGM] crash */
9167 /* Put first chess program into idle state */
9168 if (first.pr != NoProc &&
9169 (gameMode == MachinePlaysWhite ||
9170 gameMode == MachinePlaysBlack ||
9171 gameMode == TwoMachinesPlay ||
9172 gameMode == IcsPlayingWhite ||
9173 gameMode == IcsPlayingBlack ||
9174 gameMode == BeginningOfGame)) {
9175 SendToProgram("force\n", &first);
9176 if (first.usePing) {
9178 sprintf(buf, "ping %d\n", ++first.lastPing);
9179 SendToProgram(buf, &first);
9182 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9183 /* Kill off first chess program */
9184 if (first.isr != NULL)
9185 RemoveInputSource(first.isr);
9188 if (first.pr != NoProc) {
9190 DoSleep( appData.delayBeforeQuit );
9191 SendToProgram("quit\n", &first);
9192 DoSleep( appData.delayAfterQuit );
9193 DestroyChildProcess(first.pr, first.useSigterm);
9198 /* Put second chess program into idle state */
9199 if (second.pr != NoProc &&
9200 gameMode == TwoMachinesPlay) {
9201 SendToProgram("force\n", &second);
9202 if (second.usePing) {
9204 sprintf(buf, "ping %d\n", ++second.lastPing);
9205 SendToProgram(buf, &second);
9208 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9209 /* Kill off second chess program */
9210 if (second.isr != NULL)
9211 RemoveInputSource(second.isr);
9214 if (second.pr != NoProc) {
9215 DoSleep( appData.delayBeforeQuit );
9216 SendToProgram("quit\n", &second);
9217 DoSleep( appData.delayAfterQuit );
9218 DestroyChildProcess(second.pr, second.useSigterm);
9223 if (matchMode && gameMode == TwoMachinesPlay) {
9226 if (first.twoMachinesColor[0] == 'w') {
9233 if (first.twoMachinesColor[0] == 'b') {
9242 if (matchGame < appData.matchGames) {
9244 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9245 tmp = first.twoMachinesColor;
9246 first.twoMachinesColor = second.twoMachinesColor;
9247 second.twoMachinesColor = tmp;
9249 gameMode = nextGameMode;
9251 if(appData.matchPause>10000 || appData.matchPause<10)
9252 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9253 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9254 endingGame = 0; /* [HGM] crash */
9258 gameMode = nextGameMode;
9259 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9260 first.tidy, second.tidy,
9261 first.matchWins, second.matchWins,
9262 appData.matchGames - (first.matchWins + second.matchWins));
9263 DisplayFatalError(buf, 0, 0);
9266 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9267 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9269 gameMode = nextGameMode;
9271 endingGame = 0; /* [HGM] crash */
9274 /* Assumes program was just initialized (initString sent).
9275 Leaves program in force mode. */
9277 FeedMovesToProgram(cps, upto)
9278 ChessProgramState *cps;
9283 if (appData.debugMode)
9284 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9285 startedFromSetupPosition ? "position and " : "",
9286 backwardMostMove, upto, cps->which);
9287 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9288 // [HGM] variantswitch: make engine aware of new variant
9289 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9290 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9291 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9292 SendToProgram(buf, cps);
9293 currentlyInitializedVariant = gameInfo.variant;
9295 SendToProgram("force\n", cps);
9296 if (startedFromSetupPosition) {
9297 SendBoard(cps, backwardMostMove);
9298 if (appData.debugMode) {
9299 fprintf(debugFP, "feedMoves\n");
9302 for (i = backwardMostMove; i < upto; i++) {
9303 SendMoveToProgram(i, cps);
9309 ResurrectChessProgram()
9311 /* The chess program may have exited.
9312 If so, restart it and feed it all the moves made so far. */
9314 if (appData.noChessProgram || first.pr != NoProc) return;
9316 StartChessProgram(&first);
9317 InitChessProgram(&first, FALSE);
9318 FeedMovesToProgram(&first, currentMove);
9320 if (!first.sendTime) {
9321 /* can't tell gnuchess what its clock should read,
9322 so we bow to its notion. */
9324 timeRemaining[0][currentMove] = whiteTimeRemaining;
9325 timeRemaining[1][currentMove] = blackTimeRemaining;
9328 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9329 appData.icsEngineAnalyze) && first.analysisSupport) {
9330 SendToProgram("analyze\n", &first);
9331 first.analyzing = TRUE;
9344 if (appData.debugMode) {
9345 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9346 redraw, init, gameMode);
9348 CleanupTail(); // [HGM] vari: delete any stored variations
9349 pausing = pauseExamInvalid = FALSE;
9350 startedFromSetupPosition = blackPlaysFirst = FALSE;
9352 whiteFlag = blackFlag = FALSE;
9353 userOfferedDraw = FALSE;
9354 hintRequested = bookRequested = FALSE;
9355 first.maybeThinking = FALSE;
9356 second.maybeThinking = FALSE;
9357 first.bookSuspend = FALSE; // [HGM] book
9358 second.bookSuspend = FALSE;
9359 thinkOutput[0] = NULLCHAR;
9360 lastHint[0] = NULLCHAR;
9361 ClearGameInfo(&gameInfo);
9362 gameInfo.variant = StringToVariant(appData.variant);
9363 ics_user_moved = ics_clock_paused = FALSE;
9364 ics_getting_history = H_FALSE;
9366 white_holding[0] = black_holding[0] = NULLCHAR;
9367 ClearProgramStats();
9368 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9372 flipView = appData.flipView;
9373 ClearPremoveHighlights();
9375 alarmSounded = FALSE;
9377 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9378 if(appData.serverMovesName != NULL) {
9379 /* [HGM] prepare to make moves file for broadcasting */
9380 clock_t t = clock();
9381 if(serverMoves != NULL) fclose(serverMoves);
9382 serverMoves = fopen(appData.serverMovesName, "r");
9383 if(serverMoves != NULL) {
9384 fclose(serverMoves);
9385 /* delay 15 sec before overwriting, so all clients can see end */
9386 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9388 serverMoves = fopen(appData.serverMovesName, "w");
9392 gameMode = BeginningOfGame;
9394 if(appData.icsActive) gameInfo.variant = VariantNormal;
9395 currentMove = forwardMostMove = backwardMostMove = 0;
9396 InitPosition(redraw);
9397 for (i = 0; i < MAX_MOVES; i++) {
9398 if (commentList[i] != NULL) {
9399 free(commentList[i]);
9400 commentList[i] = NULL;
9404 timeRemaining[0][0] = whiteTimeRemaining;
9405 timeRemaining[1][0] = blackTimeRemaining;
9406 if (first.pr == NULL) {
9407 StartChessProgram(&first);
9410 InitChessProgram(&first, startedFromSetupPosition);
9413 DisplayMessage("", "");
9414 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9415 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9422 if (!AutoPlayOneMove())
9424 if (matchMode || appData.timeDelay == 0)
9426 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9428 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9437 int fromX, fromY, toX, toY;
9439 if (appData.debugMode) {
9440 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9443 if (gameMode != PlayFromGameFile)
9446 if (currentMove >= forwardMostMove) {
9447 gameMode = EditGame;
9450 /* [AS] Clear current move marker at the end of a game */
9451 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9456 toX = moveList[currentMove][2] - AAA;
9457 toY = moveList[currentMove][3] - ONE;
9459 if (moveList[currentMove][1] == '@') {
9460 if (appData.highlightLastMove) {
9461 SetHighlights(-1, -1, toX, toY);
9464 fromX = moveList[currentMove][0] - AAA;
9465 fromY = moveList[currentMove][1] - ONE;
9467 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9469 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9471 if (appData.highlightLastMove) {
9472 SetHighlights(fromX, fromY, toX, toY);
9475 DisplayMove(currentMove);
9476 SendMoveToProgram(currentMove++, &first);
9477 DisplayBothClocks();
9478 DrawPosition(FALSE, boards[currentMove]);
9479 // [HGM] PV info: always display, routine tests if empty
9480 DisplayComment(currentMove - 1, commentList[currentMove]);
9486 LoadGameOneMove(readAhead)
9487 ChessMove readAhead;
9489 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9490 char promoChar = NULLCHAR;
9495 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9496 gameMode != AnalyzeMode && gameMode != Training) {
9501 yyboardindex = forwardMostMove;
9502 if (readAhead != (ChessMove)0) {
9503 moveType = readAhead;
9505 if (gameFileFP == NULL)
9507 moveType = (ChessMove) yylex();
9513 if (appData.debugMode)
9514 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9517 /* append the comment but don't display it */
9518 AppendComment(currentMove, p, FALSE);
9521 case WhiteCapturesEnPassant:
9522 case BlackCapturesEnPassant:
9523 case WhitePromotionChancellor:
9524 case BlackPromotionChancellor:
9525 case WhitePromotionArchbishop:
9526 case BlackPromotionArchbishop:
9527 case WhitePromotionCentaur:
9528 case BlackPromotionCentaur:
9529 case WhitePromotionQueen:
9530 case BlackPromotionQueen:
9531 case WhitePromotionRook:
9532 case BlackPromotionRook:
9533 case WhitePromotionBishop:
9534 case BlackPromotionBishop:
9535 case WhitePromotionKnight:
9536 case BlackPromotionKnight:
9537 case WhitePromotionKing:
9538 case BlackPromotionKing:
9540 case WhiteKingSideCastle:
9541 case WhiteQueenSideCastle:
9542 case BlackKingSideCastle:
9543 case BlackQueenSideCastle:
9544 case WhiteKingSideCastleWild:
9545 case WhiteQueenSideCastleWild:
9546 case BlackKingSideCastleWild:
9547 case BlackQueenSideCastleWild:
9549 case WhiteHSideCastleFR:
9550 case WhiteASideCastleFR:
9551 case BlackHSideCastleFR:
9552 case BlackASideCastleFR:
9554 if (appData.debugMode)
9555 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9556 fromX = currentMoveString[0] - AAA;
9557 fromY = currentMoveString[1] - ONE;
9558 toX = currentMoveString[2] - AAA;
9559 toY = currentMoveString[3] - ONE;
9560 promoChar = currentMoveString[4];
9565 if (appData.debugMode)
9566 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9567 fromX = moveType == WhiteDrop ?
9568 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9569 (int) CharToPiece(ToLower(currentMoveString[0]));
9571 toX = currentMoveString[2] - AAA;
9572 toY = currentMoveString[3] - ONE;
9578 case GameUnfinished:
9579 if (appData.debugMode)
9580 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9581 p = strchr(yy_text, '{');
9582 if (p == NULL) p = strchr(yy_text, '(');
9585 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9587 q = strchr(p, *p == '{' ? '}' : ')');
9588 if (q != NULL) *q = NULLCHAR;
9591 GameEnds(moveType, p, GE_FILE);
9593 if (cmailMsgLoaded) {
9595 flipView = WhiteOnMove(currentMove);
9596 if (moveType == GameUnfinished) flipView = !flipView;
9597 if (appData.debugMode)
9598 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9602 case (ChessMove) 0: /* end of file */
9603 if (appData.debugMode)
9604 fprintf(debugFP, "Parser hit end of file\n");
9605 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9611 if (WhiteOnMove(currentMove)) {
9612 GameEnds(BlackWins, "Black mates", GE_FILE);
9614 GameEnds(WhiteWins, "White mates", GE_FILE);
9618 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9625 if (lastLoadGameStart == GNUChessGame) {
9626 /* GNUChessGames have numbers, but they aren't move numbers */
9627 if (appData.debugMode)
9628 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9629 yy_text, (int) moveType);
9630 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9632 /* else fall thru */
9637 /* Reached start of next game in file */
9638 if (appData.debugMode)
9639 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9640 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9646 if (WhiteOnMove(currentMove)) {
9647 GameEnds(BlackWins, "Black mates", GE_FILE);
9649 GameEnds(WhiteWins, "White mates", GE_FILE);
9653 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9659 case PositionDiagram: /* should not happen; ignore */
9660 case ElapsedTime: /* ignore */
9661 case NAG: /* ignore */
9662 if (appData.debugMode)
9663 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9664 yy_text, (int) moveType);
9665 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9668 if (appData.testLegality) {
9669 if (appData.debugMode)
9670 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9671 sprintf(move, _("Illegal move: %d.%s%s"),
9672 (forwardMostMove / 2) + 1,
9673 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9674 DisplayError(move, 0);
9677 if (appData.debugMode)
9678 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9679 yy_text, currentMoveString);
9680 fromX = currentMoveString[0] - AAA;
9681 fromY = currentMoveString[1] - ONE;
9682 toX = currentMoveString[2] - AAA;
9683 toY = currentMoveString[3] - ONE;
9684 promoChar = currentMoveString[4];
9689 if (appData.debugMode)
9690 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9691 sprintf(move, _("Ambiguous move: %d.%s%s"),
9692 (forwardMostMove / 2) + 1,
9693 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9694 DisplayError(move, 0);
9699 case ImpossibleMove:
9700 if (appData.debugMode)
9701 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9702 sprintf(move, _("Illegal move: %d.%s%s"),
9703 (forwardMostMove / 2) + 1,
9704 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9705 DisplayError(move, 0);
9711 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9712 DrawPosition(FALSE, boards[currentMove]);
9713 DisplayBothClocks();
9714 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9715 DisplayComment(currentMove - 1, commentList[currentMove]);
9717 (void) StopLoadGameTimer();
9719 cmailOldMove = forwardMostMove;
9722 /* currentMoveString is set as a side-effect of yylex */
9723 strcat(currentMoveString, "\n");
9724 strcpy(moveList[forwardMostMove], currentMoveString);
9726 thinkOutput[0] = NULLCHAR;
9727 MakeMove(fromX, fromY, toX, toY, promoChar);
9728 currentMove = forwardMostMove;
9733 /* Load the nth game from the given file */
9735 LoadGameFromFile(filename, n, title, useList)
9739 /*Boolean*/ int useList;
9744 if (strcmp(filename, "-") == 0) {
9748 f = fopen(filename, "rb");
9750 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9751 DisplayError(buf, errno);
9755 if (fseek(f, 0, 0) == -1) {
9756 /* f is not seekable; probably a pipe */
9759 if (useList && n == 0) {
9760 int error = GameListBuild(f);
9762 DisplayError(_("Cannot build game list"), error);
9763 } else if (!ListEmpty(&gameList) &&
9764 ((ListGame *) gameList.tailPred)->number > 1) {
9765 GameListPopUp(f, title);
9772 return LoadGame(f, n, title, FALSE);
9777 MakeRegisteredMove()
9779 int fromX, fromY, toX, toY;
9781 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9782 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9785 if (appData.debugMode)
9786 fprintf(debugFP, "Restoring %s for game %d\n",
9787 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9789 thinkOutput[0] = NULLCHAR;
9790 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9791 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9792 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9793 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9794 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9795 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9796 MakeMove(fromX, fromY, toX, toY, promoChar);
9797 ShowMove(fromX, fromY, toX, toY);
9799 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9806 if (WhiteOnMove(currentMove)) {
9807 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9809 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9814 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9821 if (WhiteOnMove(currentMove)) {
9822 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9824 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9829 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9840 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9842 CmailLoadGame(f, gameNumber, title, useList)
9850 if (gameNumber > nCmailGames) {
9851 DisplayError(_("No more games in this message"), 0);
9854 if (f == lastLoadGameFP) {
9855 int offset = gameNumber - lastLoadGameNumber;
9857 cmailMsg[0] = NULLCHAR;
9858 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9859 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9860 nCmailMovesRegistered--;
9862 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9863 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9864 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9867 if (! RegisterMove()) return FALSE;
9871 retVal = LoadGame(f, gameNumber, title, useList);
9873 /* Make move registered during previous look at this game, if any */
9874 MakeRegisteredMove();
9876 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9877 commentList[currentMove]
9878 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9879 DisplayComment(currentMove - 1, commentList[currentMove]);
9885 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9890 int gameNumber = lastLoadGameNumber + offset;
9891 if (lastLoadGameFP == NULL) {
9892 DisplayError(_("No game has been loaded yet"), 0);
9895 if (gameNumber <= 0) {
9896 DisplayError(_("Can't back up any further"), 0);
9899 if (cmailMsgLoaded) {
9900 return CmailLoadGame(lastLoadGameFP, gameNumber,
9901 lastLoadGameTitle, lastLoadGameUseList);
9903 return LoadGame(lastLoadGameFP, gameNumber,
9904 lastLoadGameTitle, lastLoadGameUseList);
9910 /* Load the nth game from open file f */
9912 LoadGame(f, gameNumber, title, useList)
9920 int gn = gameNumber;
9921 ListGame *lg = NULL;
9924 GameMode oldGameMode;
9925 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9927 if (appData.debugMode)
9928 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9930 if (gameMode == Training )
9931 SetTrainingModeOff();
9933 oldGameMode = gameMode;
9934 if (gameMode != BeginningOfGame) {
9939 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9940 fclose(lastLoadGameFP);
9944 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9947 fseek(f, lg->offset, 0);
9948 GameListHighlight(gameNumber);
9952 DisplayError(_("Game number out of range"), 0);
9957 if (fseek(f, 0, 0) == -1) {
9958 if (f == lastLoadGameFP ?
9959 gameNumber == lastLoadGameNumber + 1 :
9963 DisplayError(_("Can't seek on game file"), 0);
9969 lastLoadGameNumber = gameNumber;
9970 strcpy(lastLoadGameTitle, title);
9971 lastLoadGameUseList = useList;
9975 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9976 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9977 lg->gameInfo.black);
9979 } else if (*title != NULLCHAR) {
9980 if (gameNumber > 1) {
9981 sprintf(buf, "%s %d", title, gameNumber);
9984 DisplayTitle(title);
9988 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9989 gameMode = PlayFromGameFile;
9993 currentMove = forwardMostMove = backwardMostMove = 0;
9994 CopyBoard(boards[0], initialPosition);
9998 * Skip the first gn-1 games in the file.
9999 * Also skip over anything that precedes an identifiable
10000 * start of game marker, to avoid being confused by
10001 * garbage at the start of the file. Currently
10002 * recognized start of game markers are the move number "1",
10003 * the pattern "gnuchess .* game", the pattern
10004 * "^[#;%] [^ ]* game file", and a PGN tag block.
10005 * A game that starts with one of the latter two patterns
10006 * will also have a move number 1, possibly
10007 * following a position diagram.
10008 * 5-4-02: Let's try being more lenient and allowing a game to
10009 * start with an unnumbered move. Does that break anything?
10011 cm = lastLoadGameStart = (ChessMove) 0;
10013 yyboardindex = forwardMostMove;
10014 cm = (ChessMove) yylex();
10016 case (ChessMove) 0:
10017 if (cmailMsgLoaded) {
10018 nCmailGames = CMAIL_MAX_GAMES - gn;
10021 DisplayError(_("Game not found in file"), 0);
10028 lastLoadGameStart = cm;
10031 case MoveNumberOne:
10032 switch (lastLoadGameStart) {
10037 case MoveNumberOne:
10038 case (ChessMove) 0:
10039 gn--; /* count this game */
10040 lastLoadGameStart = cm;
10049 switch (lastLoadGameStart) {
10052 case MoveNumberOne:
10053 case (ChessMove) 0:
10054 gn--; /* count this game */
10055 lastLoadGameStart = cm;
10058 lastLoadGameStart = cm; /* game counted already */
10066 yyboardindex = forwardMostMove;
10067 cm = (ChessMove) yylex();
10068 } while (cm == PGNTag || cm == Comment);
10075 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10076 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10077 != CMAIL_OLD_RESULT) {
10079 cmailResult[ CMAIL_MAX_GAMES
10080 - gn - 1] = CMAIL_OLD_RESULT;
10086 /* Only a NormalMove can be at the start of a game
10087 * without a position diagram. */
10088 if (lastLoadGameStart == (ChessMove) 0) {
10090 lastLoadGameStart = MoveNumberOne;
10099 if (appData.debugMode)
10100 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10102 if (cm == XBoardGame) {
10103 /* Skip any header junk before position diagram and/or move 1 */
10105 yyboardindex = forwardMostMove;
10106 cm = (ChessMove) yylex();
10108 if (cm == (ChessMove) 0 ||
10109 cm == GNUChessGame || cm == XBoardGame) {
10110 /* Empty game; pretend end-of-file and handle later */
10111 cm = (ChessMove) 0;
10115 if (cm == MoveNumberOne || cm == PositionDiagram ||
10116 cm == PGNTag || cm == Comment)
10119 } else if (cm == GNUChessGame) {
10120 if (gameInfo.event != NULL) {
10121 free(gameInfo.event);
10123 gameInfo.event = StrSave(yy_text);
10126 startedFromSetupPosition = FALSE;
10127 while (cm == PGNTag) {
10128 if (appData.debugMode)
10129 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10130 err = ParsePGNTag(yy_text, &gameInfo);
10131 if (!err) numPGNTags++;
10133 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10134 if(gameInfo.variant != oldVariant) {
10135 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10136 InitPosition(TRUE);
10137 oldVariant = gameInfo.variant;
10138 if (appData.debugMode)
10139 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10143 if (gameInfo.fen != NULL) {
10144 Board initial_position;
10145 startedFromSetupPosition = TRUE;
10146 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10148 DisplayError(_("Bad FEN position in file"), 0);
10151 CopyBoard(boards[0], initial_position);
10152 if (blackPlaysFirst) {
10153 currentMove = forwardMostMove = backwardMostMove = 1;
10154 CopyBoard(boards[1], initial_position);
10155 strcpy(moveList[0], "");
10156 strcpy(parseList[0], "");
10157 timeRemaining[0][1] = whiteTimeRemaining;
10158 timeRemaining[1][1] = blackTimeRemaining;
10159 if (commentList[0] != NULL) {
10160 commentList[1] = commentList[0];
10161 commentList[0] = NULL;
10164 currentMove = forwardMostMove = backwardMostMove = 0;
10166 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10168 initialRulePlies = FENrulePlies;
10169 for( i=0; i< nrCastlingRights; i++ )
10170 initialRights[i] = initial_position[CASTLING][i];
10172 yyboardindex = forwardMostMove;
10173 free(gameInfo.fen);
10174 gameInfo.fen = NULL;
10177 yyboardindex = forwardMostMove;
10178 cm = (ChessMove) yylex();
10180 /* Handle comments interspersed among the tags */
10181 while (cm == Comment) {
10183 if (appData.debugMode)
10184 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10186 AppendComment(currentMove, p, FALSE);
10187 yyboardindex = forwardMostMove;
10188 cm = (ChessMove) yylex();
10192 /* don't rely on existence of Event tag since if game was
10193 * pasted from clipboard the Event tag may not exist
10195 if (numPGNTags > 0){
10197 if (gameInfo.variant == VariantNormal) {
10198 VariantClass v = StringToVariant(gameInfo.event);
10199 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10200 if(v < VariantShogi) gameInfo.variant = v;
10203 if( appData.autoDisplayTags ) {
10204 tags = PGNTags(&gameInfo);
10205 TagsPopUp(tags, CmailMsg());
10210 /* Make something up, but don't display it now */
10215 if (cm == PositionDiagram) {
10218 Board initial_position;
10220 if (appData.debugMode)
10221 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10223 if (!startedFromSetupPosition) {
10225 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10226 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10236 initial_position[i][j++] = CharToPiece(*p);
10239 while (*p == ' ' || *p == '\t' ||
10240 *p == '\n' || *p == '\r') p++;
10242 if (strncmp(p, "black", strlen("black"))==0)
10243 blackPlaysFirst = TRUE;
10245 blackPlaysFirst = FALSE;
10246 startedFromSetupPosition = TRUE;
10248 CopyBoard(boards[0], initial_position);
10249 if (blackPlaysFirst) {
10250 currentMove = forwardMostMove = backwardMostMove = 1;
10251 CopyBoard(boards[1], initial_position);
10252 strcpy(moveList[0], "");
10253 strcpy(parseList[0], "");
10254 timeRemaining[0][1] = whiteTimeRemaining;
10255 timeRemaining[1][1] = blackTimeRemaining;
10256 if (commentList[0] != NULL) {
10257 commentList[1] = commentList[0];
10258 commentList[0] = NULL;
10261 currentMove = forwardMostMove = backwardMostMove = 0;
10264 yyboardindex = forwardMostMove;
10265 cm = (ChessMove) yylex();
10268 if (first.pr == NoProc) {
10269 StartChessProgram(&first);
10271 InitChessProgram(&first, FALSE);
10272 SendToProgram("force\n", &first);
10273 if (startedFromSetupPosition) {
10274 SendBoard(&first, forwardMostMove);
10275 if (appData.debugMode) {
10276 fprintf(debugFP, "Load Game\n");
10278 DisplayBothClocks();
10281 /* [HGM] server: flag to write setup moves in broadcast file as one */
10282 loadFlag = appData.suppressLoadMoves;
10284 while (cm == Comment) {
10286 if (appData.debugMode)
10287 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10289 AppendComment(currentMove, p, FALSE);
10290 yyboardindex = forwardMostMove;
10291 cm = (ChessMove) yylex();
10294 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10295 cm == WhiteWins || cm == BlackWins ||
10296 cm == GameIsDrawn || cm == GameUnfinished) {
10297 DisplayMessage("", _("No moves in game"));
10298 if (cmailMsgLoaded) {
10299 if (appData.debugMode)
10300 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10304 DrawPosition(FALSE, boards[currentMove]);
10305 DisplayBothClocks();
10306 gameMode = EditGame;
10313 // [HGM] PV info: routine tests if comment empty
10314 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10315 DisplayComment(currentMove - 1, commentList[currentMove]);
10317 if (!matchMode && appData.timeDelay != 0)
10318 DrawPosition(FALSE, boards[currentMove]);
10320 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10321 programStats.ok_to_send = 1;
10324 /* if the first token after the PGN tags is a move
10325 * and not move number 1, retrieve it from the parser
10327 if (cm != MoveNumberOne)
10328 LoadGameOneMove(cm);
10330 /* load the remaining moves from the file */
10331 while (LoadGameOneMove((ChessMove)0)) {
10332 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10333 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10336 /* rewind to the start of the game */
10337 currentMove = backwardMostMove;
10339 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10341 if (oldGameMode == AnalyzeFile ||
10342 oldGameMode == AnalyzeMode) {
10343 AnalyzeFileEvent();
10346 if (matchMode || appData.timeDelay == 0) {
10348 gameMode = EditGame;
10350 } else if (appData.timeDelay > 0) {
10351 AutoPlayGameLoop();
10354 if (appData.debugMode)
10355 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10357 loadFlag = 0; /* [HGM] true game starts */
10361 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10363 ReloadPosition(offset)
10366 int positionNumber = lastLoadPositionNumber + offset;
10367 if (lastLoadPositionFP == NULL) {
10368 DisplayError(_("No position has been loaded yet"), 0);
10371 if (positionNumber <= 0) {
10372 DisplayError(_("Can't back up any further"), 0);
10375 return LoadPosition(lastLoadPositionFP, positionNumber,
10376 lastLoadPositionTitle);
10379 /* Load the nth position from the given file */
10381 LoadPositionFromFile(filename, n, title)
10389 if (strcmp(filename, "-") == 0) {
10390 return LoadPosition(stdin, n, "stdin");
10392 f = fopen(filename, "rb");
10394 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10395 DisplayError(buf, errno);
10398 return LoadPosition(f, n, title);
10403 /* Load the nth position from the given open file, and close it */
10405 LoadPosition(f, positionNumber, title)
10407 int positionNumber;
10410 char *p, line[MSG_SIZ];
10411 Board initial_position;
10412 int i, j, fenMode, pn;
10414 if (gameMode == Training )
10415 SetTrainingModeOff();
10417 if (gameMode != BeginningOfGame) {
10418 Reset(FALSE, TRUE);
10420 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10421 fclose(lastLoadPositionFP);
10423 if (positionNumber == 0) positionNumber = 1;
10424 lastLoadPositionFP = f;
10425 lastLoadPositionNumber = positionNumber;
10426 strcpy(lastLoadPositionTitle, title);
10427 if (first.pr == NoProc) {
10428 StartChessProgram(&first);
10429 InitChessProgram(&first, FALSE);
10431 pn = positionNumber;
10432 if (positionNumber < 0) {
10433 /* Negative position number means to seek to that byte offset */
10434 if (fseek(f, -positionNumber, 0) == -1) {
10435 DisplayError(_("Can't seek on position file"), 0);
10440 if (fseek(f, 0, 0) == -1) {
10441 if (f == lastLoadPositionFP ?
10442 positionNumber == lastLoadPositionNumber + 1 :
10443 positionNumber == 1) {
10446 DisplayError(_("Can't seek on position file"), 0);
10451 /* See if this file is FEN or old-style xboard */
10452 if (fgets(line, MSG_SIZ, f) == NULL) {
10453 DisplayError(_("Position not found in file"), 0);
10456 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10457 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10460 if (fenMode || line[0] == '#') pn--;
10462 /* skip positions before number pn */
10463 if (fgets(line, MSG_SIZ, f) == NULL) {
10465 DisplayError(_("Position not found in file"), 0);
10468 if (fenMode || line[0] == '#') pn--;
10473 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10474 DisplayError(_("Bad FEN position in file"), 0);
10478 (void) fgets(line, MSG_SIZ, f);
10479 (void) fgets(line, MSG_SIZ, f);
10481 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10482 (void) fgets(line, MSG_SIZ, f);
10483 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10486 initial_position[i][j++] = CharToPiece(*p);
10490 blackPlaysFirst = FALSE;
10492 (void) fgets(line, MSG_SIZ, f);
10493 if (strncmp(line, "black", strlen("black"))==0)
10494 blackPlaysFirst = TRUE;
10497 startedFromSetupPosition = TRUE;
10499 SendToProgram("force\n", &first);
10500 CopyBoard(boards[0], initial_position);
10501 if (blackPlaysFirst) {
10502 currentMove = forwardMostMove = backwardMostMove = 1;
10503 strcpy(moveList[0], "");
10504 strcpy(parseList[0], "");
10505 CopyBoard(boards[1], initial_position);
10506 DisplayMessage("", _("Black to play"));
10508 currentMove = forwardMostMove = backwardMostMove = 0;
10509 DisplayMessage("", _("White to play"));
10511 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10512 SendBoard(&first, forwardMostMove);
10513 if (appData.debugMode) {
10515 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10516 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10517 fprintf(debugFP, "Load Position\n");
10520 if (positionNumber > 1) {
10521 sprintf(line, "%s %d", title, positionNumber);
10522 DisplayTitle(line);
10524 DisplayTitle(title);
10526 gameMode = EditGame;
10529 timeRemaining[0][1] = whiteTimeRemaining;
10530 timeRemaining[1][1] = blackTimeRemaining;
10531 DrawPosition(FALSE, boards[currentMove]);
10538 CopyPlayerNameIntoFileName(dest, src)
10541 while (*src != NULLCHAR && *src != ',') {
10546 *(*dest)++ = *src++;
10551 char *DefaultFileName(ext)
10554 static char def[MSG_SIZ];
10557 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10559 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10561 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10570 /* Save the current game to the given file */
10572 SaveGameToFile(filename, append)
10579 if (strcmp(filename, "-") == 0) {
10580 return SaveGame(stdout, 0, NULL);
10582 f = fopen(filename, append ? "a" : "w");
10584 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10585 DisplayError(buf, errno);
10588 return SaveGame(f, 0, NULL);
10597 static char buf[MSG_SIZ];
10600 p = strchr(str, ' ');
10601 if (p == NULL) return str;
10602 strncpy(buf, str, p - str);
10603 buf[p - str] = NULLCHAR;
10607 #define PGN_MAX_LINE 75
10609 #define PGN_SIDE_WHITE 0
10610 #define PGN_SIDE_BLACK 1
10613 static int FindFirstMoveOutOfBook( int side )
10617 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10618 int index = backwardMostMove;
10619 int has_book_hit = 0;
10621 if( (index % 2) != side ) {
10625 while( index < forwardMostMove ) {
10626 /* Check to see if engine is in book */
10627 int depth = pvInfoList[index].depth;
10628 int score = pvInfoList[index].score;
10634 else if( score == 0 && depth == 63 ) {
10635 in_book = 1; /* Zappa */
10637 else if( score == 2 && depth == 99 ) {
10638 in_book = 1; /* Abrok */
10641 has_book_hit += in_book;
10657 void GetOutOfBookInfo( char * buf )
10661 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10663 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10664 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10668 if( oob[0] >= 0 || oob[1] >= 0 ) {
10669 for( i=0; i<2; i++ ) {
10673 if( i > 0 && oob[0] >= 0 ) {
10674 strcat( buf, " " );
10677 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10678 sprintf( buf+strlen(buf), "%s%.2f",
10679 pvInfoList[idx].score >= 0 ? "+" : "",
10680 pvInfoList[idx].score / 100.0 );
10686 /* Save game in PGN style and close the file */
10691 int i, offset, linelen, newblock;
10695 int movelen, numlen, blank;
10696 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10698 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10700 tm = time((time_t *) NULL);
10702 PrintPGNTags(f, &gameInfo);
10704 if (backwardMostMove > 0 || startedFromSetupPosition) {
10705 char *fen = PositionToFEN(backwardMostMove, NULL);
10706 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10707 fprintf(f, "\n{--------------\n");
10708 PrintPosition(f, backwardMostMove);
10709 fprintf(f, "--------------}\n");
10713 /* [AS] Out of book annotation */
10714 if( appData.saveOutOfBookInfo ) {
10717 GetOutOfBookInfo( buf );
10719 if( buf[0] != '\0' ) {
10720 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10727 i = backwardMostMove;
10731 while (i < forwardMostMove) {
10732 /* Print comments preceding this move */
10733 if (commentList[i] != NULL) {
10734 if (linelen > 0) fprintf(f, "\n");
10735 fprintf(f, "%s", commentList[i]);
10740 /* Format move number */
10741 if ((i % 2) == 0) {
10742 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10745 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10747 numtext[0] = NULLCHAR;
10750 numlen = strlen(numtext);
10753 /* Print move number */
10754 blank = linelen > 0 && numlen > 0;
10755 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10764 fprintf(f, "%s", numtext);
10768 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10769 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10772 blank = linelen > 0 && movelen > 0;
10773 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10782 fprintf(f, "%s", move_buffer);
10783 linelen += movelen;
10785 /* [AS] Add PV info if present */
10786 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10787 /* [HGM] add time */
10788 char buf[MSG_SIZ]; int seconds;
10790 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10792 if( seconds <= 0) buf[0] = 0; else
10793 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10794 seconds = (seconds + 4)/10; // round to full seconds
10795 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10796 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10799 sprintf( move_buffer, "{%s%.2f/%d%s}",
10800 pvInfoList[i].score >= 0 ? "+" : "",
10801 pvInfoList[i].score / 100.0,
10802 pvInfoList[i].depth,
10805 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10807 /* Print score/depth */
10808 blank = linelen > 0 && movelen > 0;
10809 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10818 fprintf(f, "%s", move_buffer);
10819 linelen += movelen;
10825 /* Start a new line */
10826 if (linelen > 0) fprintf(f, "\n");
10828 /* Print comments after last move */
10829 if (commentList[i] != NULL) {
10830 fprintf(f, "%s\n", commentList[i]);
10834 if (gameInfo.resultDetails != NULL &&
10835 gameInfo.resultDetails[0] != NULLCHAR) {
10836 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10837 PGNResult(gameInfo.result));
10839 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10843 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10847 /* Save game in old style and close the file */
10849 SaveGameOldStyle(f)
10855 tm = time((time_t *) NULL);
10857 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10860 if (backwardMostMove > 0 || startedFromSetupPosition) {
10861 fprintf(f, "\n[--------------\n");
10862 PrintPosition(f, backwardMostMove);
10863 fprintf(f, "--------------]\n");
10868 i = backwardMostMove;
10869 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10871 while (i < forwardMostMove) {
10872 if (commentList[i] != NULL) {
10873 fprintf(f, "[%s]\n", commentList[i]);
10876 if ((i % 2) == 1) {
10877 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10880 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10882 if (commentList[i] != NULL) {
10886 if (i >= forwardMostMove) {
10890 fprintf(f, "%s\n", parseList[i]);
10895 if (commentList[i] != NULL) {
10896 fprintf(f, "[%s]\n", commentList[i]);
10899 /* This isn't really the old style, but it's close enough */
10900 if (gameInfo.resultDetails != NULL &&
10901 gameInfo.resultDetails[0] != NULLCHAR) {
10902 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10903 gameInfo.resultDetails);
10905 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10912 /* Save the current game to open file f and close the file */
10914 SaveGame(f, dummy, dummy2)
10919 if (gameMode == EditPosition) EditPositionDone(TRUE);
10920 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10921 if (appData.oldSaveStyle)
10922 return SaveGameOldStyle(f);
10924 return SaveGamePGN(f);
10927 /* Save the current position to the given file */
10929 SavePositionToFile(filename)
10935 if (strcmp(filename, "-") == 0) {
10936 return SavePosition(stdout, 0, NULL);
10938 f = fopen(filename, "a");
10940 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10941 DisplayError(buf, errno);
10944 SavePosition(f, 0, NULL);
10950 /* Save the current position to the given open file and close the file */
10952 SavePosition(f, dummy, dummy2)
10960 if (gameMode == EditPosition) EditPositionDone(TRUE);
10961 if (appData.oldSaveStyle) {
10962 tm = time((time_t *) NULL);
10964 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10966 fprintf(f, "[--------------\n");
10967 PrintPosition(f, currentMove);
10968 fprintf(f, "--------------]\n");
10970 fen = PositionToFEN(currentMove, NULL);
10971 fprintf(f, "%s\n", fen);
10979 ReloadCmailMsgEvent(unregister)
10983 static char *inFilename = NULL;
10984 static char *outFilename;
10986 struct stat inbuf, outbuf;
10989 /* Any registered moves are unregistered if unregister is set, */
10990 /* i.e. invoked by the signal handler */
10992 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10993 cmailMoveRegistered[i] = FALSE;
10994 if (cmailCommentList[i] != NULL) {
10995 free(cmailCommentList[i]);
10996 cmailCommentList[i] = NULL;
10999 nCmailMovesRegistered = 0;
11002 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11003 cmailResult[i] = CMAIL_NOT_RESULT;
11007 if (inFilename == NULL) {
11008 /* Because the filenames are static they only get malloced once */
11009 /* and they never get freed */
11010 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11011 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11013 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11014 sprintf(outFilename, "%s.out", appData.cmailGameName);
11017 status = stat(outFilename, &outbuf);
11019 cmailMailedMove = FALSE;
11021 status = stat(inFilename, &inbuf);
11022 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11025 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11026 counts the games, notes how each one terminated, etc.
11028 It would be nice to remove this kludge and instead gather all
11029 the information while building the game list. (And to keep it
11030 in the game list nodes instead of having a bunch of fixed-size
11031 parallel arrays.) Note this will require getting each game's
11032 termination from the PGN tags, as the game list builder does
11033 not process the game moves. --mann
11035 cmailMsgLoaded = TRUE;
11036 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11038 /* Load first game in the file or popup game menu */
11039 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11041 #endif /* !WIN32 */
11049 char string[MSG_SIZ];
11051 if ( cmailMailedMove
11052 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11053 return TRUE; /* Allow free viewing */
11056 /* Unregister move to ensure that we don't leave RegisterMove */
11057 /* with the move registered when the conditions for registering no */
11059 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11060 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11061 nCmailMovesRegistered --;
11063 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11065 free(cmailCommentList[lastLoadGameNumber - 1]);
11066 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11070 if (cmailOldMove == -1) {
11071 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11075 if (currentMove > cmailOldMove + 1) {
11076 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11080 if (currentMove < cmailOldMove) {
11081 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11085 if (forwardMostMove > currentMove) {
11086 /* Silently truncate extra moves */
11090 if ( (currentMove == cmailOldMove + 1)
11091 || ( (currentMove == cmailOldMove)
11092 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11093 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11094 if (gameInfo.result != GameUnfinished) {
11095 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11098 if (commentList[currentMove] != NULL) {
11099 cmailCommentList[lastLoadGameNumber - 1]
11100 = StrSave(commentList[currentMove]);
11102 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11104 if (appData.debugMode)
11105 fprintf(debugFP, "Saving %s for game %d\n",
11106 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11109 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11111 f = fopen(string, "w");
11112 if (appData.oldSaveStyle) {
11113 SaveGameOldStyle(f); /* also closes the file */
11115 sprintf(string, "%s.pos.out", appData.cmailGameName);
11116 f = fopen(string, "w");
11117 SavePosition(f, 0, NULL); /* also closes the file */
11119 fprintf(f, "{--------------\n");
11120 PrintPosition(f, currentMove);
11121 fprintf(f, "--------------}\n\n");
11123 SaveGame(f, 0, NULL); /* also closes the file*/
11126 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11127 nCmailMovesRegistered ++;
11128 } else if (nCmailGames == 1) {
11129 DisplayError(_("You have not made a move yet"), 0);
11140 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11141 FILE *commandOutput;
11142 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11143 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11149 if (! cmailMsgLoaded) {
11150 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11154 if (nCmailGames == nCmailResults) {
11155 DisplayError(_("No unfinished games"), 0);
11159 #if CMAIL_PROHIBIT_REMAIL
11160 if (cmailMailedMove) {
11161 sprintf(msg, _("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);
11162 DisplayError(msg, 0);
11167 if (! (cmailMailedMove || RegisterMove())) return;
11169 if ( cmailMailedMove
11170 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11171 sprintf(string, partCommandString,
11172 appData.debugMode ? " -v" : "", appData.cmailGameName);
11173 commandOutput = popen(string, "r");
11175 if (commandOutput == NULL) {
11176 DisplayError(_("Failed to invoke cmail"), 0);
11178 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11179 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11181 if (nBuffers > 1) {
11182 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11183 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11184 nBytes = MSG_SIZ - 1;
11186 (void) memcpy(msg, buffer, nBytes);
11188 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11190 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11191 cmailMailedMove = TRUE; /* Prevent >1 moves */
11194 for (i = 0; i < nCmailGames; i ++) {
11195 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11200 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11202 sprintf(buffer, "%s/%s.%s.archive",
11204 appData.cmailGameName,
11206 LoadGameFromFile(buffer, 1, buffer, FALSE);
11207 cmailMsgLoaded = FALSE;
11211 DisplayInformation(msg);
11212 pclose(commandOutput);
11215 if ((*cmailMsg) != '\0') {
11216 DisplayInformation(cmailMsg);
11221 #endif /* !WIN32 */
11230 int prependComma = 0;
11232 char string[MSG_SIZ]; /* Space for game-list */
11235 if (!cmailMsgLoaded) return "";
11237 if (cmailMailedMove) {
11238 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11240 /* Create a list of games left */
11241 sprintf(string, "[");
11242 for (i = 0; i < nCmailGames; i ++) {
11243 if (! ( cmailMoveRegistered[i]
11244 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11245 if (prependComma) {
11246 sprintf(number, ",%d", i + 1);
11248 sprintf(number, "%d", i + 1);
11252 strcat(string, number);
11255 strcat(string, "]");
11257 if (nCmailMovesRegistered + nCmailResults == 0) {
11258 switch (nCmailGames) {
11261 _("Still need to make move for game\n"));
11266 _("Still need to make moves for both games\n"));
11271 _("Still need to make moves for all %d games\n"),
11276 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11279 _("Still need to make a move for game %s\n"),
11284 if (nCmailResults == nCmailGames) {
11285 sprintf(cmailMsg, _("No unfinished games\n"));
11287 sprintf(cmailMsg, _("Ready to send mail\n"));
11293 _("Still need to make moves for games %s\n"),
11305 if (gameMode == Training)
11306 SetTrainingModeOff();
11309 cmailMsgLoaded = FALSE;
11310 if (appData.icsActive) {
11311 SendToICS(ics_prefix);
11312 SendToICS("refresh\n");
11322 /* Give up on clean exit */
11326 /* Keep trying for clean exit */
11330 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11332 if (telnetISR != NULL) {
11333 RemoveInputSource(telnetISR);
11335 if (icsPR != NoProc) {
11336 DestroyChildProcess(icsPR, TRUE);
11339 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11340 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11342 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11343 /* make sure this other one finishes before killing it! */
11344 if(endingGame) { int count = 0;
11345 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11346 while(endingGame && count++ < 10) DoSleep(1);
11347 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11350 /* Kill off chess programs */
11351 if (first.pr != NoProc) {
11354 DoSleep( appData.delayBeforeQuit );
11355 SendToProgram("quit\n", &first);
11356 DoSleep( appData.delayAfterQuit );
11357 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11359 if (second.pr != NoProc) {
11360 DoSleep( appData.delayBeforeQuit );
11361 SendToProgram("quit\n", &second);
11362 DoSleep( appData.delayAfterQuit );
11363 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11365 if (first.isr != NULL) {
11366 RemoveInputSource(first.isr);
11368 if (second.isr != NULL) {
11369 RemoveInputSource(second.isr);
11372 ShutDownFrontEnd();
11379 if (appData.debugMode)
11380 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11384 if (gameMode == MachinePlaysWhite ||
11385 gameMode == MachinePlaysBlack) {
11388 DisplayBothClocks();
11390 if (gameMode == PlayFromGameFile) {
11391 if (appData.timeDelay >= 0)
11392 AutoPlayGameLoop();
11393 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11394 Reset(FALSE, TRUE);
11395 SendToICS(ics_prefix);
11396 SendToICS("refresh\n");
11397 } else if (currentMove < forwardMostMove) {
11398 ForwardInner(forwardMostMove);
11400 pauseExamInvalid = FALSE;
11402 switch (gameMode) {
11406 pauseExamForwardMostMove = forwardMostMove;
11407 pauseExamInvalid = FALSE;
11410 case IcsPlayingWhite:
11411 case IcsPlayingBlack:
11415 case PlayFromGameFile:
11416 (void) StopLoadGameTimer();
11420 case BeginningOfGame:
11421 if (appData.icsActive) return;
11422 /* else fall through */
11423 case MachinePlaysWhite:
11424 case MachinePlaysBlack:
11425 case TwoMachinesPlay:
11426 if (forwardMostMove == 0)
11427 return; /* don't pause if no one has moved */
11428 if ((gameMode == MachinePlaysWhite &&
11429 !WhiteOnMove(forwardMostMove)) ||
11430 (gameMode == MachinePlaysBlack &&
11431 WhiteOnMove(forwardMostMove))) {
11444 char title[MSG_SIZ];
11446 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11447 strcpy(title, _("Edit comment"));
11449 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11450 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11451 parseList[currentMove - 1]);
11454 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11461 char *tags = PGNTags(&gameInfo);
11462 EditTagsPopUp(tags);
11469 if (appData.noChessProgram || gameMode == AnalyzeMode)
11472 if (gameMode != AnalyzeFile) {
11473 if (!appData.icsEngineAnalyze) {
11475 if (gameMode != EditGame) return;
11477 ResurrectChessProgram();
11478 SendToProgram("analyze\n", &first);
11479 first.analyzing = TRUE;
11480 /*first.maybeThinking = TRUE;*/
11481 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11482 EngineOutputPopUp();
11484 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11489 StartAnalysisClock();
11490 GetTimeMark(&lastNodeCountTime);
11497 if (appData.noChessProgram || gameMode == AnalyzeFile)
11500 if (gameMode != AnalyzeMode) {
11502 if (gameMode != EditGame) return;
11503 ResurrectChessProgram();
11504 SendToProgram("analyze\n", &first);
11505 first.analyzing = TRUE;
11506 /*first.maybeThinking = TRUE;*/
11507 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11508 EngineOutputPopUp();
11510 gameMode = AnalyzeFile;
11515 StartAnalysisClock();
11516 GetTimeMark(&lastNodeCountTime);
11521 MachineWhiteEvent()
11524 char *bookHit = NULL;
11526 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11530 if (gameMode == PlayFromGameFile ||
11531 gameMode == TwoMachinesPlay ||
11532 gameMode == Training ||
11533 gameMode == AnalyzeMode ||
11534 gameMode == EndOfGame)
11537 if (gameMode == EditPosition)
11538 EditPositionDone(TRUE);
11540 if (!WhiteOnMove(currentMove)) {
11541 DisplayError(_("It is not White's turn"), 0);
11545 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11548 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11549 gameMode == AnalyzeFile)
11552 ResurrectChessProgram(); /* in case it isn't running */
11553 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11554 gameMode = MachinePlaysWhite;
11557 gameMode = MachinePlaysWhite;
11561 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11563 if (first.sendName) {
11564 sprintf(buf, "name %s\n", gameInfo.black);
11565 SendToProgram(buf, &first);
11567 if (first.sendTime) {
11568 if (first.useColors) {
11569 SendToProgram("black\n", &first); /*gnu kludge*/
11571 SendTimeRemaining(&first, TRUE);
11573 if (first.useColors) {
11574 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11576 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11577 SetMachineThinkingEnables();
11578 first.maybeThinking = TRUE;
11582 if (appData.autoFlipView && !flipView) {
11583 flipView = !flipView;
11584 DrawPosition(FALSE, NULL);
11585 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11588 if(bookHit) { // [HGM] book: simulate book reply
11589 static char bookMove[MSG_SIZ]; // a bit generous?
11591 programStats.nodes = programStats.depth = programStats.time =
11592 programStats.score = programStats.got_only_move = 0;
11593 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11595 strcpy(bookMove, "move ");
11596 strcat(bookMove, bookHit);
11597 HandleMachineMove(bookMove, &first);
11602 MachineBlackEvent()
11605 char *bookHit = NULL;
11607 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11611 if (gameMode == PlayFromGameFile ||
11612 gameMode == TwoMachinesPlay ||
11613 gameMode == Training ||
11614 gameMode == AnalyzeMode ||
11615 gameMode == EndOfGame)
11618 if (gameMode == EditPosition)
11619 EditPositionDone(TRUE);
11621 if (WhiteOnMove(currentMove)) {
11622 DisplayError(_("It is not Black's turn"), 0);
11626 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11629 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11630 gameMode == AnalyzeFile)
11633 ResurrectChessProgram(); /* in case it isn't running */
11634 gameMode = MachinePlaysBlack;
11638 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11640 if (first.sendName) {
11641 sprintf(buf, "name %s\n", gameInfo.white);
11642 SendToProgram(buf, &first);
11644 if (first.sendTime) {
11645 if (first.useColors) {
11646 SendToProgram("white\n", &first); /*gnu kludge*/
11648 SendTimeRemaining(&first, FALSE);
11650 if (first.useColors) {
11651 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11653 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11654 SetMachineThinkingEnables();
11655 first.maybeThinking = TRUE;
11658 if (appData.autoFlipView && flipView) {
11659 flipView = !flipView;
11660 DrawPosition(FALSE, NULL);
11661 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11663 if(bookHit) { // [HGM] book: simulate book reply
11664 static char bookMove[MSG_SIZ]; // a bit generous?
11666 programStats.nodes = programStats.depth = programStats.time =
11667 programStats.score = programStats.got_only_move = 0;
11668 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11670 strcpy(bookMove, "move ");
11671 strcat(bookMove, bookHit);
11672 HandleMachineMove(bookMove, &first);
11678 DisplayTwoMachinesTitle()
11681 if (appData.matchGames > 0) {
11682 if (first.twoMachinesColor[0] == 'w') {
11683 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11684 gameInfo.white, gameInfo.black,
11685 first.matchWins, second.matchWins,
11686 matchGame - 1 - (first.matchWins + second.matchWins));
11688 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689 gameInfo.white, gameInfo.black,
11690 second.matchWins, first.matchWins,
11691 matchGame - 1 - (first.matchWins + second.matchWins));
11694 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11700 TwoMachinesEvent P((void))
11704 ChessProgramState *onmove;
11705 char *bookHit = NULL;
11707 if (appData.noChessProgram) return;
11709 switch (gameMode) {
11710 case TwoMachinesPlay:
11712 case MachinePlaysWhite:
11713 case MachinePlaysBlack:
11714 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11715 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11719 case BeginningOfGame:
11720 case PlayFromGameFile:
11723 if (gameMode != EditGame) return;
11726 EditPositionDone(TRUE);
11737 // forwardMostMove = currentMove;
11738 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11739 ResurrectChessProgram(); /* in case first program isn't running */
11741 if (second.pr == NULL) {
11742 StartChessProgram(&second);
11743 if (second.protocolVersion == 1) {
11744 TwoMachinesEventIfReady();
11746 /* kludge: allow timeout for initial "feature" command */
11748 DisplayMessage("", _("Starting second chess program"));
11749 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11753 DisplayMessage("", "");
11754 InitChessProgram(&second, FALSE);
11755 SendToProgram("force\n", &second);
11756 if (startedFromSetupPosition) {
11757 SendBoard(&second, backwardMostMove);
11758 if (appData.debugMode) {
11759 fprintf(debugFP, "Two Machines\n");
11762 for (i = backwardMostMove; i < forwardMostMove; i++) {
11763 SendMoveToProgram(i, &second);
11766 gameMode = TwoMachinesPlay;
11770 DisplayTwoMachinesTitle();
11772 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11778 SendToProgram(first.computerString, &first);
11779 if (first.sendName) {
11780 sprintf(buf, "name %s\n", second.tidy);
11781 SendToProgram(buf, &first);
11783 SendToProgram(second.computerString, &second);
11784 if (second.sendName) {
11785 sprintf(buf, "name %s\n", first.tidy);
11786 SendToProgram(buf, &second);
11790 if (!first.sendTime || !second.sendTime) {
11791 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11792 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11794 if (onmove->sendTime) {
11795 if (onmove->useColors) {
11796 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11798 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11800 if (onmove->useColors) {
11801 SendToProgram(onmove->twoMachinesColor, onmove);
11803 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11804 // SendToProgram("go\n", onmove);
11805 onmove->maybeThinking = TRUE;
11806 SetMachineThinkingEnables();
11810 if(bookHit) { // [HGM] book: simulate book reply
11811 static char bookMove[MSG_SIZ]; // a bit generous?
11813 programStats.nodes = programStats.depth = programStats.time =
11814 programStats.score = programStats.got_only_move = 0;
11815 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11817 strcpy(bookMove, "move ");
11818 strcat(bookMove, bookHit);
11819 savedMessage = bookMove; // args for deferred call
11820 savedState = onmove;
11821 ScheduleDelayedEvent(DeferredBookMove, 1);
11828 if (gameMode == Training) {
11829 SetTrainingModeOff();
11830 gameMode = PlayFromGameFile;
11831 DisplayMessage("", _("Training mode off"));
11833 gameMode = Training;
11834 animateTraining = appData.animate;
11836 /* make sure we are not already at the end of the game */
11837 if (currentMove < forwardMostMove) {
11838 SetTrainingModeOn();
11839 DisplayMessage("", _("Training mode on"));
11841 gameMode = PlayFromGameFile;
11842 DisplayError(_("Already at end of game"), 0);
11851 if (!appData.icsActive) return;
11852 switch (gameMode) {
11853 case IcsPlayingWhite:
11854 case IcsPlayingBlack:
11857 case BeginningOfGame:
11865 EditPositionDone(TRUE);
11878 gameMode = IcsIdle;
11889 switch (gameMode) {
11891 SetTrainingModeOff();
11893 case MachinePlaysWhite:
11894 case MachinePlaysBlack:
11895 case BeginningOfGame:
11896 SendToProgram("force\n", &first);
11897 SetUserThinkingEnables();
11899 case PlayFromGameFile:
11900 (void) StopLoadGameTimer();
11901 if (gameFileFP != NULL) {
11906 EditPositionDone(TRUE);
11911 SendToProgram("force\n", &first);
11913 case TwoMachinesPlay:
11914 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11915 ResurrectChessProgram();
11916 SetUserThinkingEnables();
11919 ResurrectChessProgram();
11921 case IcsPlayingBlack:
11922 case IcsPlayingWhite:
11923 DisplayError(_("Warning: You are still playing a game"), 0);
11926 DisplayError(_("Warning: You are still observing a game"), 0);
11929 DisplayError(_("Warning: You are still examining a game"), 0);
11940 first.offeredDraw = second.offeredDraw = 0;
11942 if (gameMode == PlayFromGameFile) {
11943 whiteTimeRemaining = timeRemaining[0][currentMove];
11944 blackTimeRemaining = timeRemaining[1][currentMove];
11948 if (gameMode == MachinePlaysWhite ||
11949 gameMode == MachinePlaysBlack ||
11950 gameMode == TwoMachinesPlay ||
11951 gameMode == EndOfGame) {
11952 i = forwardMostMove;
11953 while (i > currentMove) {
11954 SendToProgram("undo\n", &first);
11957 whiteTimeRemaining = timeRemaining[0][currentMove];
11958 blackTimeRemaining = timeRemaining[1][currentMove];
11959 DisplayBothClocks();
11960 if (whiteFlag || blackFlag) {
11961 whiteFlag = blackFlag = 0;
11966 gameMode = EditGame;
11973 EditPositionEvent()
11975 if (gameMode == EditPosition) {
11981 if (gameMode != EditGame) return;
11983 gameMode = EditPosition;
11986 if (currentMove > 0)
11987 CopyBoard(boards[0], boards[currentMove]);
11989 blackPlaysFirst = !WhiteOnMove(currentMove);
11991 currentMove = forwardMostMove = backwardMostMove = 0;
11992 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11999 /* [DM] icsEngineAnalyze - possible call from other functions */
12000 if (appData.icsEngineAnalyze) {
12001 appData.icsEngineAnalyze = FALSE;
12003 DisplayMessage("",_("Close ICS engine analyze..."));
12005 if (first.analysisSupport && first.analyzing) {
12006 SendToProgram("exit\n", &first);
12007 first.analyzing = FALSE;
12009 thinkOutput[0] = NULLCHAR;
12013 EditPositionDone(Boolean fakeRights)
12015 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12017 startedFromSetupPosition = TRUE;
12018 InitChessProgram(&first, FALSE);
12019 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12020 boards[0][EP_STATUS] = EP_NONE;
12021 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12022 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12023 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12024 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12025 } else boards[0][CASTLING][2] = NoRights;
12026 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12027 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12028 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12029 } else boards[0][CASTLING][5] = NoRights;
12031 SendToProgram("force\n", &first);
12032 if (blackPlaysFirst) {
12033 strcpy(moveList[0], "");
12034 strcpy(parseList[0], "");
12035 currentMove = forwardMostMove = backwardMostMove = 1;
12036 CopyBoard(boards[1], boards[0]);
12038 currentMove = forwardMostMove = backwardMostMove = 0;
12040 SendBoard(&first, forwardMostMove);
12041 if (appData.debugMode) {
12042 fprintf(debugFP, "EditPosDone\n");
12045 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12046 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12047 gameMode = EditGame;
12049 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12050 ClearHighlights(); /* [AS] */
12053 /* Pause for `ms' milliseconds */
12054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12064 } while (SubtractTimeMarks(&m2, &m1) < ms);
12067 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12069 SendMultiLineToICS(buf)
12072 char temp[MSG_SIZ+1], *p;
12079 strncpy(temp, buf, len);
12084 if (*p == '\n' || *p == '\r')
12089 strcat(temp, "\n");
12091 SendToPlayer(temp, strlen(temp));
12095 SetWhiteToPlayEvent()
12097 if (gameMode == EditPosition) {
12098 blackPlaysFirst = FALSE;
12099 DisplayBothClocks(); /* works because currentMove is 0 */
12100 } else if (gameMode == IcsExamining) {
12101 SendToICS(ics_prefix);
12102 SendToICS("tomove white\n");
12107 SetBlackToPlayEvent()
12109 if (gameMode == EditPosition) {
12110 blackPlaysFirst = TRUE;
12111 currentMove = 1; /* kludge */
12112 DisplayBothClocks();
12114 } else if (gameMode == IcsExamining) {
12115 SendToICS(ics_prefix);
12116 SendToICS("tomove black\n");
12121 EditPositionMenuEvent(selection, x, y)
12122 ChessSquare selection;
12126 ChessSquare piece = boards[0][y][x];
12128 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12130 switch (selection) {
12132 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12133 SendToICS(ics_prefix);
12134 SendToICS("bsetup clear\n");
12135 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12136 SendToICS(ics_prefix);
12137 SendToICS("clearboard\n");
12139 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12140 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12141 for (y = 0; y < BOARD_HEIGHT; y++) {
12142 if (gameMode == IcsExamining) {
12143 if (boards[currentMove][y][x] != EmptySquare) {
12144 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12149 boards[0][y][x] = p;
12154 if (gameMode == EditPosition) {
12155 DrawPosition(FALSE, boards[0]);
12160 SetWhiteToPlayEvent();
12164 SetBlackToPlayEvent();
12168 if (gameMode == IcsExamining) {
12169 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12170 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12173 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12174 if(x == BOARD_LEFT-2) {
12175 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12176 boards[0][y][1] = 0;
12178 if(x == BOARD_RGHT+1) {
12179 if(y >= gameInfo.holdingsSize) break;
12180 boards[0][y][BOARD_WIDTH-2] = 0;
12183 boards[0][y][x] = EmptySquare;
12184 DrawPosition(FALSE, boards[0]);
12189 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12190 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12191 selection = (ChessSquare) (PROMOTED piece);
12192 } else if(piece == EmptySquare) selection = WhiteSilver;
12193 else selection = (ChessSquare)((int)piece - 1);
12197 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12198 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12199 selection = (ChessSquare) (DEMOTED piece);
12200 } else if(piece == EmptySquare) selection = BlackSilver;
12201 else selection = (ChessSquare)((int)piece + 1);
12206 if(gameInfo.variant == VariantShatranj ||
12207 gameInfo.variant == VariantXiangqi ||
12208 gameInfo.variant == VariantCourier ||
12209 gameInfo.variant == VariantMakruk )
12210 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12215 if(gameInfo.variant == VariantXiangqi)
12216 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12217 if(gameInfo.variant == VariantKnightmate)
12218 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12221 if (gameMode == IcsExamining) {
12222 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12223 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12224 PieceToChar(selection), AAA + x, ONE + y);
12227 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12229 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12230 n = PieceToNumber(selection - BlackPawn);
12231 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12232 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12233 boards[0][BOARD_HEIGHT-1-n][1]++;
12235 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12236 n = PieceToNumber(selection);
12237 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12238 boards[0][n][BOARD_WIDTH-1] = selection;
12239 boards[0][n][BOARD_WIDTH-2]++;
12242 boards[0][y][x] = selection;
12243 DrawPosition(TRUE, boards[0]);
12251 DropMenuEvent(selection, x, y)
12252 ChessSquare selection;
12255 ChessMove moveType;
12257 switch (gameMode) {
12258 case IcsPlayingWhite:
12259 case MachinePlaysBlack:
12260 if (!WhiteOnMove(currentMove)) {
12261 DisplayMoveError(_("It is Black's turn"));
12264 moveType = WhiteDrop;
12266 case IcsPlayingBlack:
12267 case MachinePlaysWhite:
12268 if (WhiteOnMove(currentMove)) {
12269 DisplayMoveError(_("It is White's turn"));
12272 moveType = BlackDrop;
12275 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12281 if (moveType == BlackDrop && selection < BlackPawn) {
12282 selection = (ChessSquare) ((int) selection
12283 + (int) BlackPawn - (int) WhitePawn);
12285 if (boards[currentMove][y][x] != EmptySquare) {
12286 DisplayMoveError(_("That square is occupied"));
12290 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12296 /* Accept a pending offer of any kind from opponent */
12298 if (appData.icsActive) {
12299 SendToICS(ics_prefix);
12300 SendToICS("accept\n");
12301 } else if (cmailMsgLoaded) {
12302 if (currentMove == cmailOldMove &&
12303 commentList[cmailOldMove] != NULL &&
12304 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12305 "Black offers a draw" : "White offers a draw")) {
12307 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12308 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12310 DisplayError(_("There is no pending offer on this move"), 0);
12311 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12314 /* Not used for offers from chess program */
12321 /* Decline a pending offer of any kind from opponent */
12323 if (appData.icsActive) {
12324 SendToICS(ics_prefix);
12325 SendToICS("decline\n");
12326 } else if (cmailMsgLoaded) {
12327 if (currentMove == cmailOldMove &&
12328 commentList[cmailOldMove] != NULL &&
12329 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12330 "Black offers a draw" : "White offers a draw")) {
12332 AppendComment(cmailOldMove, "Draw declined", TRUE);
12333 DisplayComment(cmailOldMove - 1, "Draw declined");
12336 DisplayError(_("There is no pending offer on this move"), 0);
12339 /* Not used for offers from chess program */
12346 /* Issue ICS rematch command */
12347 if (appData.icsActive) {
12348 SendToICS(ics_prefix);
12349 SendToICS("rematch\n");
12356 /* Call your opponent's flag (claim a win on time) */
12357 if (appData.icsActive) {
12358 SendToICS(ics_prefix);
12359 SendToICS("flag\n");
12361 switch (gameMode) {
12364 case MachinePlaysWhite:
12367 GameEnds(GameIsDrawn, "Both players ran out of time",
12370 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12372 DisplayError(_("Your opponent is not out of time"), 0);
12375 case MachinePlaysBlack:
12378 GameEnds(GameIsDrawn, "Both players ran out of time",
12381 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12383 DisplayError(_("Your opponent is not out of time"), 0);
12393 /* Offer draw or accept pending draw offer from opponent */
12395 if (appData.icsActive) {
12396 /* Note: tournament rules require draw offers to be
12397 made after you make your move but before you punch
12398 your clock. Currently ICS doesn't let you do that;
12399 instead, you immediately punch your clock after making
12400 a move, but you can offer a draw at any time. */
12402 SendToICS(ics_prefix);
12403 SendToICS("draw\n");
12404 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12405 } else if (cmailMsgLoaded) {
12406 if (currentMove == cmailOldMove &&
12407 commentList[cmailOldMove] != NULL &&
12408 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12409 "Black offers a draw" : "White offers a draw")) {
12410 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12411 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12412 } else if (currentMove == cmailOldMove + 1) {
12413 char *offer = WhiteOnMove(cmailOldMove) ?
12414 "White offers a draw" : "Black offers a draw";
12415 AppendComment(currentMove, offer, TRUE);
12416 DisplayComment(currentMove - 1, offer);
12417 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12419 DisplayError(_("You must make your move before offering a draw"), 0);
12420 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12422 } else if (first.offeredDraw) {
12423 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12425 if (first.sendDrawOffers) {
12426 SendToProgram("draw\n", &first);
12427 userOfferedDraw = TRUE;
12435 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12437 if (appData.icsActive) {
12438 SendToICS(ics_prefix);
12439 SendToICS("adjourn\n");
12441 /* Currently GNU Chess doesn't offer or accept Adjourns */
12449 /* Offer Abort or accept pending Abort offer from opponent */
12451 if (appData.icsActive) {
12452 SendToICS(ics_prefix);
12453 SendToICS("abort\n");
12455 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12462 /* Resign. You can do this even if it's not your turn. */
12464 if (appData.icsActive) {
12465 SendToICS(ics_prefix);
12466 SendToICS("resign\n");
12468 switch (gameMode) {
12469 case MachinePlaysWhite:
12470 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12472 case MachinePlaysBlack:
12473 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12476 if (cmailMsgLoaded) {
12478 if (WhiteOnMove(cmailOldMove)) {
12479 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12481 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12483 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12494 StopObservingEvent()
12496 /* Stop observing current games */
12497 SendToICS(ics_prefix);
12498 SendToICS("unobserve\n");
12502 StopExaminingEvent()
12504 /* Stop observing current game */
12505 SendToICS(ics_prefix);
12506 SendToICS("unexamine\n");
12510 ForwardInner(target)
12515 if (appData.debugMode)
12516 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12517 target, currentMove, forwardMostMove);
12519 if (gameMode == EditPosition)
12522 if (gameMode == PlayFromGameFile && !pausing)
12525 if (gameMode == IcsExamining && pausing)
12526 limit = pauseExamForwardMostMove;
12528 limit = forwardMostMove;
12530 if (target > limit) target = limit;
12532 if (target > 0 && moveList[target - 1][0]) {
12533 int fromX, fromY, toX, toY;
12534 toX = moveList[target - 1][2] - AAA;
12535 toY = moveList[target - 1][3] - ONE;
12536 if (moveList[target - 1][1] == '@') {
12537 if (appData.highlightLastMove) {
12538 SetHighlights(-1, -1, toX, toY);
12541 fromX = moveList[target - 1][0] - AAA;
12542 fromY = moveList[target - 1][1] - ONE;
12543 if (target == currentMove + 1) {
12544 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12546 if (appData.highlightLastMove) {
12547 SetHighlights(fromX, fromY, toX, toY);
12551 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12552 gameMode == Training || gameMode == PlayFromGameFile ||
12553 gameMode == AnalyzeFile) {
12554 while (currentMove < target) {
12555 SendMoveToProgram(currentMove++, &first);
12558 currentMove = target;
12561 if (gameMode == EditGame || gameMode == EndOfGame) {
12562 whiteTimeRemaining = timeRemaining[0][currentMove];
12563 blackTimeRemaining = timeRemaining[1][currentMove];
12565 DisplayBothClocks();
12566 DisplayMove(currentMove - 1);
12567 DrawPosition(FALSE, boards[currentMove]);
12568 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12569 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12570 DisplayComment(currentMove - 1, commentList[currentMove]);
12578 if (gameMode == IcsExamining && !pausing) {
12579 SendToICS(ics_prefix);
12580 SendToICS("forward\n");
12582 ForwardInner(currentMove + 1);
12589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12590 /* to optimze, we temporarily turn off analysis mode while we feed
12591 * the remaining moves to the engine. Otherwise we get analysis output
12594 if (first.analysisSupport) {
12595 SendToProgram("exit\nforce\n", &first);
12596 first.analyzing = FALSE;
12600 if (gameMode == IcsExamining && !pausing) {
12601 SendToICS(ics_prefix);
12602 SendToICS("forward 999999\n");
12604 ForwardInner(forwardMostMove);
12607 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12608 /* we have fed all the moves, so reactivate analysis mode */
12609 SendToProgram("analyze\n", &first);
12610 first.analyzing = TRUE;
12611 /*first.maybeThinking = TRUE;*/
12612 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12617 BackwardInner(target)
12620 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12622 if (appData.debugMode)
12623 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12624 target, currentMove, forwardMostMove);
12626 if (gameMode == EditPosition) return;
12627 if (currentMove <= backwardMostMove) {
12629 DrawPosition(full_redraw, boards[currentMove]);
12632 if (gameMode == PlayFromGameFile && !pausing)
12635 if (moveList[target][0]) {
12636 int fromX, fromY, toX, toY;
12637 toX = moveList[target][2] - AAA;
12638 toY = moveList[target][3] - ONE;
12639 if (moveList[target][1] == '@') {
12640 if (appData.highlightLastMove) {
12641 SetHighlights(-1, -1, toX, toY);
12644 fromX = moveList[target][0] - AAA;
12645 fromY = moveList[target][1] - ONE;
12646 if (target == currentMove - 1) {
12647 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12649 if (appData.highlightLastMove) {
12650 SetHighlights(fromX, fromY, toX, toY);
12654 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12655 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12656 while (currentMove > target) {
12657 SendToProgram("undo\n", &first);
12661 currentMove = target;
12664 if (gameMode == EditGame || gameMode == EndOfGame) {
12665 whiteTimeRemaining = timeRemaining[0][currentMove];
12666 blackTimeRemaining = timeRemaining[1][currentMove];
12668 DisplayBothClocks();
12669 DisplayMove(currentMove - 1);
12670 DrawPosition(full_redraw, boards[currentMove]);
12671 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12672 // [HGM] PV info: routine tests if comment empty
12673 DisplayComment(currentMove - 1, commentList[currentMove]);
12679 if (gameMode == IcsExamining && !pausing) {
12680 SendToICS(ics_prefix);
12681 SendToICS("backward\n");
12683 BackwardInner(currentMove - 1);
12690 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12691 /* to optimize, we temporarily turn off analysis mode while we undo
12692 * all the moves. Otherwise we get analysis output after each undo.
12694 if (first.analysisSupport) {
12695 SendToProgram("exit\nforce\n", &first);
12696 first.analyzing = FALSE;
12700 if (gameMode == IcsExamining && !pausing) {
12701 SendToICS(ics_prefix);
12702 SendToICS("backward 999999\n");
12704 BackwardInner(backwardMostMove);
12707 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12708 /* we have fed all the moves, so reactivate analysis mode */
12709 SendToProgram("analyze\n", &first);
12710 first.analyzing = TRUE;
12711 /*first.maybeThinking = TRUE;*/
12712 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12719 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12720 if (to >= forwardMostMove) to = forwardMostMove;
12721 if (to <= backwardMostMove) to = backwardMostMove;
12722 if (to < currentMove) {
12730 RevertEvent(Boolean annotate)
12732 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12735 if (gameMode != IcsExamining) {
12736 DisplayError(_("You are not examining a game"), 0);
12740 DisplayError(_("You can't revert while pausing"), 0);
12743 SendToICS(ics_prefix);
12744 SendToICS("revert\n");
12750 switch (gameMode) {
12751 case MachinePlaysWhite:
12752 case MachinePlaysBlack:
12753 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12754 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12757 if (forwardMostMove < 2) return;
12758 currentMove = forwardMostMove = forwardMostMove - 2;
12759 whiteTimeRemaining = timeRemaining[0][currentMove];
12760 blackTimeRemaining = timeRemaining[1][currentMove];
12761 DisplayBothClocks();
12762 DisplayMove(currentMove - 1);
12763 ClearHighlights();/*!! could figure this out*/
12764 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12765 SendToProgram("remove\n", &first);
12766 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12769 case BeginningOfGame:
12773 case IcsPlayingWhite:
12774 case IcsPlayingBlack:
12775 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12776 SendToICS(ics_prefix);
12777 SendToICS("takeback 2\n");
12779 SendToICS(ics_prefix);
12780 SendToICS("takeback 1\n");
12789 ChessProgramState *cps;
12791 switch (gameMode) {
12792 case MachinePlaysWhite:
12793 if (!WhiteOnMove(forwardMostMove)) {
12794 DisplayError(_("It is your turn"), 0);
12799 case MachinePlaysBlack:
12800 if (WhiteOnMove(forwardMostMove)) {
12801 DisplayError(_("It is your turn"), 0);
12806 case TwoMachinesPlay:
12807 if (WhiteOnMove(forwardMostMove) ==
12808 (first.twoMachinesColor[0] == 'w')) {
12814 case BeginningOfGame:
12818 SendToProgram("?\n", cps);
12822 TruncateGameEvent()
12825 if (gameMode != EditGame) return;
12832 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12833 if (forwardMostMove > currentMove) {
12834 if (gameInfo.resultDetails != NULL) {
12835 free(gameInfo.resultDetails);
12836 gameInfo.resultDetails = NULL;
12837 gameInfo.result = GameUnfinished;
12839 forwardMostMove = currentMove;
12840 HistorySet(parseList, backwardMostMove, forwardMostMove,
12848 if (appData.noChessProgram) return;
12849 switch (gameMode) {
12850 case MachinePlaysWhite:
12851 if (WhiteOnMove(forwardMostMove)) {
12852 DisplayError(_("Wait until your turn"), 0);
12856 case BeginningOfGame:
12857 case MachinePlaysBlack:
12858 if (!WhiteOnMove(forwardMostMove)) {
12859 DisplayError(_("Wait until your turn"), 0);
12864 DisplayError(_("No hint available"), 0);
12867 SendToProgram("hint\n", &first);
12868 hintRequested = TRUE;
12874 if (appData.noChessProgram) return;
12875 switch (gameMode) {
12876 case MachinePlaysWhite:
12877 if (WhiteOnMove(forwardMostMove)) {
12878 DisplayError(_("Wait until your turn"), 0);
12882 case BeginningOfGame:
12883 case MachinePlaysBlack:
12884 if (!WhiteOnMove(forwardMostMove)) {
12885 DisplayError(_("Wait until your turn"), 0);
12890 EditPositionDone(TRUE);
12892 case TwoMachinesPlay:
12897 SendToProgram("bk\n", &first);
12898 bookOutput[0] = NULLCHAR;
12899 bookRequested = TRUE;
12905 char *tags = PGNTags(&gameInfo);
12906 TagsPopUp(tags, CmailMsg());
12910 /* end button procedures */
12913 PrintPosition(fp, move)
12919 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12920 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12921 char c = PieceToChar(boards[move][i][j]);
12922 fputc(c == 'x' ? '.' : c, fp);
12923 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12926 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12927 fprintf(fp, "white to play\n");
12929 fprintf(fp, "black to play\n");
12936 if (gameInfo.white != NULL) {
12937 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12943 /* Find last component of program's own name, using some heuristics */
12945 TidyProgramName(prog, host, buf)
12946 char *prog, *host, buf[MSG_SIZ];
12949 int local = (strcmp(host, "localhost") == 0);
12950 while (!local && (p = strchr(prog, ';')) != NULL) {
12952 while (*p == ' ') p++;
12955 if (*prog == '"' || *prog == '\'') {
12956 q = strchr(prog + 1, *prog);
12958 q = strchr(prog, ' ');
12960 if (q == NULL) q = prog + strlen(prog);
12962 while (p >= prog && *p != '/' && *p != '\\') p--;
12964 if(p == prog && *p == '"') p++;
12965 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12966 memcpy(buf, p, q - p);
12967 buf[q - p] = NULLCHAR;
12975 TimeControlTagValue()
12978 if (!appData.clockMode) {
12980 } else if (movesPerSession > 0) {
12981 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12982 } else if (timeIncrement == 0) {
12983 sprintf(buf, "%ld", timeControl/1000);
12985 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12987 return StrSave(buf);
12993 /* This routine is used only for certain modes */
12994 VariantClass v = gameInfo.variant;
12995 ChessMove r = GameUnfinished;
12998 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12999 r = gameInfo.result;
13000 p = gameInfo.resultDetails;
13001 gameInfo.resultDetails = NULL;
13003 ClearGameInfo(&gameInfo);
13004 gameInfo.variant = v;
13006 switch (gameMode) {
13007 case MachinePlaysWhite:
13008 gameInfo.event = StrSave( appData.pgnEventHeader );
13009 gameInfo.site = StrSave(HostName());
13010 gameInfo.date = PGNDate();
13011 gameInfo.round = StrSave("-");
13012 gameInfo.white = StrSave(first.tidy);
13013 gameInfo.black = StrSave(UserName());
13014 gameInfo.timeControl = TimeControlTagValue();
13017 case MachinePlaysBlack:
13018 gameInfo.event = StrSave( appData.pgnEventHeader );
13019 gameInfo.site = StrSave(HostName());
13020 gameInfo.date = PGNDate();
13021 gameInfo.round = StrSave("-");
13022 gameInfo.white = StrSave(UserName());
13023 gameInfo.black = StrSave(first.tidy);
13024 gameInfo.timeControl = TimeControlTagValue();
13027 case TwoMachinesPlay:
13028 gameInfo.event = StrSave( appData.pgnEventHeader );
13029 gameInfo.site = StrSave(HostName());
13030 gameInfo.date = PGNDate();
13031 if (matchGame > 0) {
13033 sprintf(buf, "%d", matchGame);
13034 gameInfo.round = StrSave(buf);
13036 gameInfo.round = StrSave("-");
13038 if (first.twoMachinesColor[0] == 'w') {
13039 gameInfo.white = StrSave(first.tidy);
13040 gameInfo.black = StrSave(second.tidy);
13042 gameInfo.white = StrSave(second.tidy);
13043 gameInfo.black = StrSave(first.tidy);
13045 gameInfo.timeControl = TimeControlTagValue();
13049 gameInfo.event = StrSave("Edited game");
13050 gameInfo.site = StrSave(HostName());
13051 gameInfo.date = PGNDate();
13052 gameInfo.round = StrSave("-");
13053 gameInfo.white = StrSave("-");
13054 gameInfo.black = StrSave("-");
13055 gameInfo.result = r;
13056 gameInfo.resultDetails = p;
13060 gameInfo.event = StrSave("Edited position");
13061 gameInfo.site = StrSave(HostName());
13062 gameInfo.date = PGNDate();
13063 gameInfo.round = StrSave("-");
13064 gameInfo.white = StrSave("-");
13065 gameInfo.black = StrSave("-");
13068 case IcsPlayingWhite:
13069 case IcsPlayingBlack:
13074 case PlayFromGameFile:
13075 gameInfo.event = StrSave("Game from non-PGN file");
13076 gameInfo.site = StrSave(HostName());
13077 gameInfo.date = PGNDate();
13078 gameInfo.round = StrSave("-");
13079 gameInfo.white = StrSave("?");
13080 gameInfo.black = StrSave("?");
13089 ReplaceComment(index, text)
13095 while (*text == '\n') text++;
13096 len = strlen(text);
13097 while (len > 0 && text[len - 1] == '\n') len--;
13099 if (commentList[index] != NULL)
13100 free(commentList[index]);
13103 commentList[index] = NULL;
13106 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13107 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13108 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13109 commentList[index] = (char *) malloc(len + 2);
13110 strncpy(commentList[index], text, len);
13111 commentList[index][len] = '\n';
13112 commentList[index][len + 1] = NULLCHAR;
13114 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13116 commentList[index] = (char *) malloc(len + 6);
13117 strcpy(commentList[index], "{\n");
13118 strncpy(commentList[index]+2, text, len);
13119 commentList[index][len+2] = NULLCHAR;
13120 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13121 strcat(commentList[index], "\n}\n");
13135 if (ch == '\r') continue;
13137 } while (ch != '\0');
13141 AppendComment(index, text, addBraces)
13144 Boolean addBraces; // [HGM] braces: tells if we should add {}
13149 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13150 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13153 while (*text == '\n') text++;
13154 len = strlen(text);
13155 while (len > 0 && text[len - 1] == '\n') len--;
13157 if (len == 0) return;
13159 if (commentList[index] != NULL) {
13160 old = commentList[index];
13161 oldlen = strlen(old);
13162 while(commentList[index][oldlen-1] == '\n')
13163 commentList[index][--oldlen] = NULLCHAR;
13164 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13165 strcpy(commentList[index], old);
13167 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13168 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13169 if(addBraces) addBraces = FALSE; else { text++; len--; }
13170 while (*text == '\n') { text++; len--; }
13171 commentList[index][--oldlen] = NULLCHAR;
13173 if(addBraces) strcat(commentList[index], "\n{\n");
13174 else strcat(commentList[index], "\n");
13175 strcat(commentList[index], text);
13176 if(addBraces) strcat(commentList[index], "\n}\n");
13177 else strcat(commentList[index], "\n");
13179 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13181 strcpy(commentList[index], "{\n");
13182 else commentList[index][0] = NULLCHAR;
13183 strcat(commentList[index], text);
13184 strcat(commentList[index], "\n");
13185 if(addBraces) strcat(commentList[index], "}\n");
13189 static char * FindStr( char * text, char * sub_text )
13191 char * result = strstr( text, sub_text );
13193 if( result != NULL ) {
13194 result += strlen( sub_text );
13200 /* [AS] Try to extract PV info from PGN comment */
13201 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13202 char *GetInfoFromComment( int index, char * text )
13206 if( text != NULL && index > 0 ) {
13209 int time = -1, sec = 0, deci;
13210 char * s_eval = FindStr( text, "[%eval " );
13211 char * s_emt = FindStr( text, "[%emt " );
13213 if( s_eval != NULL || s_emt != NULL ) {
13217 if( s_eval != NULL ) {
13218 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13222 if( delim != ']' ) {
13227 if( s_emt != NULL ) {
13232 /* We expect something like: [+|-]nnn.nn/dd */
13235 if(*text != '{') return text; // [HGM] braces: must be normal comment
13237 sep = strchr( text, '/' );
13238 if( sep == NULL || sep < (text+4) ) {
13242 time = -1; sec = -1; deci = -1;
13243 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13244 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13245 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13246 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13250 if( score_lo < 0 || score_lo >= 100 ) {
13254 if(sec >= 0) time = 600*time + 10*sec; else
13255 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13257 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13259 /* [HGM] PV time: now locate end of PV info */
13260 while( *++sep >= '0' && *sep <= '9'); // strip depth
13262 while( *++sep >= '0' && *sep <= '9'); // strip time
13264 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13266 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13267 while(*sep == ' ') sep++;
13278 pvInfoList[index-1].depth = depth;
13279 pvInfoList[index-1].score = score;
13280 pvInfoList[index-1].time = 10*time; // centi-sec
13281 if(*sep == '}') *sep = 0; else *--sep = '{';
13287 SendToProgram(message, cps)
13289 ChessProgramState *cps;
13291 int count, outCount, error;
13294 if (cps->pr == NULL) return;
13297 if (appData.debugMode) {
13300 fprintf(debugFP, "%ld >%-6s: %s",
13301 SubtractTimeMarks(&now, &programStartTime),
13302 cps->which, message);
13305 count = strlen(message);
13306 outCount = OutputToProcess(cps->pr, message, count, &error);
13307 if (outCount < count && !exiting
13308 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13309 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13310 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13311 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13312 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13313 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13315 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13317 gameInfo.resultDetails = StrSave(buf);
13319 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13324 ReceiveFromProgram(isr, closure, message, count, error)
13325 InputSourceRef isr;
13333 ChessProgramState *cps = (ChessProgramState *)closure;
13335 if (isr != cps->isr) return; /* Killed intentionally */
13339 _("Error: %s chess program (%s) exited unexpectedly"),
13340 cps->which, cps->program);
13341 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13342 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13343 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13344 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13346 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13348 gameInfo.resultDetails = StrSave(buf);
13350 RemoveInputSource(cps->isr);
13351 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13354 _("Error reading from %s chess program (%s)"),
13355 cps->which, cps->program);
13356 RemoveInputSource(cps->isr);
13358 /* [AS] Program is misbehaving badly... kill it */
13359 if( count == -2 ) {
13360 DestroyChildProcess( cps->pr, 9 );
13364 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13369 if ((end_str = strchr(message, '\r')) != NULL)
13370 *end_str = NULLCHAR;
13371 if ((end_str = strchr(message, '\n')) != NULL)
13372 *end_str = NULLCHAR;
13374 if (appData.debugMode) {
13375 TimeMark now; int print = 1;
13376 char *quote = ""; char c; int i;
13378 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13379 char start = message[0];
13380 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13381 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13382 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13383 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13384 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13385 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13386 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13387 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13388 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13389 print = (appData.engineComments >= 2);
13391 message[0] = start; // restore original message
13395 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13396 SubtractTimeMarks(&now, &programStartTime), cps->which,
13402 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13403 if (appData.icsEngineAnalyze) {
13404 if (strstr(message, "whisper") != NULL ||
13405 strstr(message, "kibitz") != NULL ||
13406 strstr(message, "tellics") != NULL) return;
13409 HandleMachineMove(message, cps);
13414 SendTimeControl(cps, mps, tc, inc, sd, st)
13415 ChessProgramState *cps;
13416 int mps, inc, sd, st;
13422 if( timeControl_2 > 0 ) {
13423 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13424 tc = timeControl_2;
13427 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13428 inc /= cps->timeOdds;
13429 st /= cps->timeOdds;
13431 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13434 /* Set exact time per move, normally using st command */
13435 if (cps->stKludge) {
13436 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13438 if (seconds == 0) {
13439 sprintf(buf, "level 1 %d\n", st/60);
13441 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13444 sprintf(buf, "st %d\n", st);
13447 /* Set conventional or incremental time control, using level command */
13448 if (seconds == 0) {
13449 /* Note old gnuchess bug -- minutes:seconds used to not work.
13450 Fixed in later versions, but still avoid :seconds
13451 when seconds is 0. */
13452 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13454 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13455 seconds, inc/1000);
13458 SendToProgram(buf, cps);
13460 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13461 /* Orthogonally, limit search to given depth */
13463 if (cps->sdKludge) {
13464 sprintf(buf, "depth\n%d\n", sd);
13466 sprintf(buf, "sd %d\n", sd);
13468 SendToProgram(buf, cps);
13471 if(cps->nps > 0) { /* [HGM] nps */
13472 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13474 sprintf(buf, "nps %d\n", cps->nps);
13475 SendToProgram(buf, cps);
13480 ChessProgramState *WhitePlayer()
13481 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13483 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13484 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13490 SendTimeRemaining(cps, machineWhite)
13491 ChessProgramState *cps;
13492 int /*boolean*/ machineWhite;
13494 char message[MSG_SIZ];
13497 /* Note: this routine must be called when the clocks are stopped
13498 or when they have *just* been set or switched; otherwise
13499 it will be off by the time since the current tick started.
13501 if (machineWhite) {
13502 time = whiteTimeRemaining / 10;
13503 otime = blackTimeRemaining / 10;
13505 time = blackTimeRemaining / 10;
13506 otime = whiteTimeRemaining / 10;
13508 /* [HGM] translate opponent's time by time-odds factor */
13509 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13510 if (appData.debugMode) {
13511 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13514 if (time <= 0) time = 1;
13515 if (otime <= 0) otime = 1;
13517 sprintf(message, "time %ld\n", time);
13518 SendToProgram(message, cps);
13520 sprintf(message, "otim %ld\n", otime);
13521 SendToProgram(message, cps);
13525 BoolFeature(p, name, loc, cps)
13529 ChessProgramState *cps;
13532 int len = strlen(name);
13534 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13536 sscanf(*p, "%d", &val);
13538 while (**p && **p != ' ') (*p)++;
13539 sprintf(buf, "accepted %s\n", name);
13540 SendToProgram(buf, cps);
13547 IntFeature(p, name, loc, cps)
13551 ChessProgramState *cps;
13554 int len = strlen(name);
13555 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13557 sscanf(*p, "%d", loc);
13558 while (**p && **p != ' ') (*p)++;
13559 sprintf(buf, "accepted %s\n", name);
13560 SendToProgram(buf, cps);
13567 StringFeature(p, name, loc, cps)
13571 ChessProgramState *cps;
13574 int len = strlen(name);
13575 if (strncmp((*p), name, len) == 0
13576 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13578 sscanf(*p, "%[^\"]", loc);
13579 while (**p && **p != '\"') (*p)++;
13580 if (**p == '\"') (*p)++;
13581 sprintf(buf, "accepted %s\n", name);
13582 SendToProgram(buf, cps);
13589 ParseOption(Option *opt, ChessProgramState *cps)
13590 // [HGM] options: process the string that defines an engine option, and determine
13591 // name, type, default value, and allowed value range
13593 char *p, *q, buf[MSG_SIZ];
13594 int n, min = (-1)<<31, max = 1<<31, def;
13596 if(p = strstr(opt->name, " -spin ")) {
13597 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598 if(max < min) max = min; // enforce consistency
13599 if(def < min) def = min;
13600 if(def > max) def = max;
13605 } else if((p = strstr(opt->name, " -slider "))) {
13606 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13607 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13608 if(max < min) max = min; // enforce consistency
13609 if(def < min) def = min;
13610 if(def > max) def = max;
13614 opt->type = Spin; // Slider;
13615 } else if((p = strstr(opt->name, " -string "))) {
13616 opt->textValue = p+9;
13617 opt->type = TextBox;
13618 } else if((p = strstr(opt->name, " -file "))) {
13619 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13620 opt->textValue = p+7;
13621 opt->type = TextBox; // FileName;
13622 } else if((p = strstr(opt->name, " -path "))) {
13623 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13624 opt->textValue = p+7;
13625 opt->type = TextBox; // PathName;
13626 } else if(p = strstr(opt->name, " -check ")) {
13627 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13628 opt->value = (def != 0);
13629 opt->type = CheckBox;
13630 } else if(p = strstr(opt->name, " -combo ")) {
13631 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13632 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13633 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13634 opt->value = n = 0;
13635 while(q = StrStr(q, " /// ")) {
13636 n++; *q = 0; // count choices, and null-terminate each of them
13638 if(*q == '*') { // remember default, which is marked with * prefix
13642 cps->comboList[cps->comboCnt++] = q;
13644 cps->comboList[cps->comboCnt++] = NULL;
13646 opt->type = ComboBox;
13647 } else if(p = strstr(opt->name, " -button")) {
13648 opt->type = Button;
13649 } else if(p = strstr(opt->name, " -save")) {
13650 opt->type = SaveButton;
13651 } else return FALSE;
13652 *p = 0; // terminate option name
13653 // now look if the command-line options define a setting for this engine option.
13654 if(cps->optionSettings && cps->optionSettings[0])
13655 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13656 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13657 sprintf(buf, "option %s", p);
13658 if(p = strstr(buf, ",")) *p = 0;
13660 SendToProgram(buf, cps);
13666 FeatureDone(cps, val)
13667 ChessProgramState* cps;
13670 DelayedEventCallback cb = GetDelayedEvent();
13671 if ((cb == InitBackEnd3 && cps == &first) ||
13672 (cb == TwoMachinesEventIfReady && cps == &second)) {
13673 CancelDelayedEvent();
13674 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13676 cps->initDone = val;
13679 /* Parse feature command from engine */
13681 ParseFeatures(args, cps)
13683 ChessProgramState *cps;
13691 while (*p == ' ') p++;
13692 if (*p == NULLCHAR) return;
13694 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13695 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13696 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13697 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13698 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13699 if (BoolFeature(&p, "reuse", &val, cps)) {
13700 /* Engine can disable reuse, but can't enable it if user said no */
13701 if (!val) cps->reuse = FALSE;
13704 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13705 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13706 if (gameMode == TwoMachinesPlay) {
13707 DisplayTwoMachinesTitle();
13713 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13714 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13715 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13716 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13717 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13718 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13719 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13720 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13721 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13722 if (IntFeature(&p, "done", &val, cps)) {
13723 FeatureDone(cps, val);
13726 /* Added by Tord: */
13727 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13728 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13729 /* End of additions by Tord */
13731 /* [HGM] added features: */
13732 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13733 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13734 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13735 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13736 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13737 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13738 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13739 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13740 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13741 SendToProgram(buf, cps);
13744 if(cps->nrOptions >= MAX_OPTIONS) {
13746 sprintf(buf, "%s engine has too many options\n", cps->which);
13747 DisplayError(buf, 0);
13751 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13752 /* End of additions by HGM */
13754 /* unknown feature: complain and skip */
13756 while (*q && *q != '=') q++;
13757 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13758 SendToProgram(buf, cps);
13764 while (*p && *p != '\"') p++;
13765 if (*p == '\"') p++;
13767 while (*p && *p != ' ') p++;
13775 PeriodicUpdatesEvent(newState)
13778 if (newState == appData.periodicUpdates)
13781 appData.periodicUpdates=newState;
13783 /* Display type changes, so update it now */
13784 // DisplayAnalysis();
13786 /* Get the ball rolling again... */
13788 AnalysisPeriodicEvent(1);
13789 StartAnalysisClock();
13794 PonderNextMoveEvent(newState)
13797 if (newState == appData.ponderNextMove) return;
13798 if (gameMode == EditPosition) EditPositionDone(TRUE);
13800 SendToProgram("hard\n", &first);
13801 if (gameMode == TwoMachinesPlay) {
13802 SendToProgram("hard\n", &second);
13805 SendToProgram("easy\n", &first);
13806 thinkOutput[0] = NULLCHAR;
13807 if (gameMode == TwoMachinesPlay) {
13808 SendToProgram("easy\n", &second);
13811 appData.ponderNextMove = newState;
13815 NewSettingEvent(option, command, value)
13821 if (gameMode == EditPosition) EditPositionDone(TRUE);
13822 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13823 SendToProgram(buf, &first);
13824 if (gameMode == TwoMachinesPlay) {
13825 SendToProgram(buf, &second);
13830 ShowThinkingEvent()
13831 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13833 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13834 int newState = appData.showThinking
13835 // [HGM] thinking: other features now need thinking output as well
13836 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13838 if (oldState == newState) return;
13839 oldState = newState;
13840 if (gameMode == EditPosition) EditPositionDone(TRUE);
13842 SendToProgram("post\n", &first);
13843 if (gameMode == TwoMachinesPlay) {
13844 SendToProgram("post\n", &second);
13847 SendToProgram("nopost\n", &first);
13848 thinkOutput[0] = NULLCHAR;
13849 if (gameMode == TwoMachinesPlay) {
13850 SendToProgram("nopost\n", &second);
13853 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13857 AskQuestionEvent(title, question, replyPrefix, which)
13858 char *title; char *question; char *replyPrefix; char *which;
13860 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13861 if (pr == NoProc) return;
13862 AskQuestion(title, question, replyPrefix, pr);
13866 DisplayMove(moveNumber)
13869 char message[MSG_SIZ];
13871 char cpThinkOutput[MSG_SIZ];
13873 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13875 if (moveNumber == forwardMostMove - 1 ||
13876 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13878 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13880 if (strchr(cpThinkOutput, '\n')) {
13881 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13884 *cpThinkOutput = NULLCHAR;
13887 /* [AS] Hide thinking from human user */
13888 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13889 *cpThinkOutput = NULLCHAR;
13890 if( thinkOutput[0] != NULLCHAR ) {
13893 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13894 cpThinkOutput[i] = '.';
13896 cpThinkOutput[i] = NULLCHAR;
13897 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13901 if (moveNumber == forwardMostMove - 1 &&
13902 gameInfo.resultDetails != NULL) {
13903 if (gameInfo.resultDetails[0] == NULLCHAR) {
13904 sprintf(res, " %s", PGNResult(gameInfo.result));
13906 sprintf(res, " {%s} %s",
13907 gameInfo.resultDetails, PGNResult(gameInfo.result));
13913 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13914 DisplayMessage(res, cpThinkOutput);
13916 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13917 WhiteOnMove(moveNumber) ? " " : ".. ",
13918 parseList[moveNumber], res);
13919 DisplayMessage(message, cpThinkOutput);
13924 DisplayComment(moveNumber, text)
13928 char title[MSG_SIZ];
13929 char buf[8000]; // comment can be long!
13932 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13933 strcpy(title, "Comment");
13935 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13936 WhiteOnMove(moveNumber) ? " " : ".. ",
13937 parseList[moveNumber]);
13939 // [HGM] PV info: display PV info together with (or as) comment
13940 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13941 if(text == NULL) text = "";
13942 score = pvInfoList[moveNumber].score;
13943 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13944 depth, (pvInfoList[moveNumber].time+50)/100, text);
13947 if (text != NULL && (appData.autoDisplayComment || commentUp))
13948 CommentPopUp(title, text);
13951 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13952 * might be busy thinking or pondering. It can be omitted if your
13953 * gnuchess is configured to stop thinking immediately on any user
13954 * input. However, that gnuchess feature depends on the FIONREAD
13955 * ioctl, which does not work properly on some flavors of Unix.
13959 ChessProgramState *cps;
13962 if (!cps->useSigint) return;
13963 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13964 switch (gameMode) {
13965 case MachinePlaysWhite:
13966 case MachinePlaysBlack:
13967 case TwoMachinesPlay:
13968 case IcsPlayingWhite:
13969 case IcsPlayingBlack:
13972 /* Skip if we know it isn't thinking */
13973 if (!cps->maybeThinking) return;
13974 if (appData.debugMode)
13975 fprintf(debugFP, "Interrupting %s\n", cps->which);
13976 InterruptChildProcess(cps->pr);
13977 cps->maybeThinking = FALSE;
13982 #endif /*ATTENTION*/
13988 if (whiteTimeRemaining <= 0) {
13991 if (appData.icsActive) {
13992 if (appData.autoCallFlag &&
13993 gameMode == IcsPlayingBlack && !blackFlag) {
13994 SendToICS(ics_prefix);
13995 SendToICS("flag\n");
13999 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14001 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14002 if (appData.autoCallFlag) {
14003 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14010 if (blackTimeRemaining <= 0) {
14013 if (appData.icsActive) {
14014 if (appData.autoCallFlag &&
14015 gameMode == IcsPlayingWhite && !whiteFlag) {
14016 SendToICS(ics_prefix);
14017 SendToICS("flag\n");
14021 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14023 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14024 if (appData.autoCallFlag) {
14025 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14038 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14039 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14042 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14044 if ( !WhiteOnMove(forwardMostMove) )
14045 /* White made time control */
14046 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14047 /* [HGM] time odds: correct new time quota for time odds! */
14048 / WhitePlayer()->timeOdds;
14050 /* Black made time control */
14051 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14052 / WhitePlayer()->other->timeOdds;
14056 DisplayBothClocks()
14058 int wom = gameMode == EditPosition ?
14059 !blackPlaysFirst : WhiteOnMove(currentMove);
14060 DisplayWhiteClock(whiteTimeRemaining, wom);
14061 DisplayBlackClock(blackTimeRemaining, !wom);
14065 /* Timekeeping seems to be a portability nightmare. I think everyone
14066 has ftime(), but I'm really not sure, so I'm including some ifdefs
14067 to use other calls if you don't. Clocks will be less accurate if
14068 you have neither ftime nor gettimeofday.
14071 /* VS 2008 requires the #include outside of the function */
14072 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14073 #include <sys/timeb.h>
14076 /* Get the current time as a TimeMark */
14081 #if HAVE_GETTIMEOFDAY
14083 struct timeval timeVal;
14084 struct timezone timeZone;
14086 gettimeofday(&timeVal, &timeZone);
14087 tm->sec = (long) timeVal.tv_sec;
14088 tm->ms = (int) (timeVal.tv_usec / 1000L);
14090 #else /*!HAVE_GETTIMEOFDAY*/
14093 // include <sys/timeb.h> / moved to just above start of function
14094 struct timeb timeB;
14097 tm->sec = (long) timeB.time;
14098 tm->ms = (int) timeB.millitm;
14100 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14101 tm->sec = (long) time(NULL);
14107 /* Return the difference in milliseconds between two
14108 time marks. We assume the difference will fit in a long!
14111 SubtractTimeMarks(tm2, tm1)
14112 TimeMark *tm2, *tm1;
14114 return 1000L*(tm2->sec - tm1->sec) +
14115 (long) (tm2->ms - tm1->ms);
14120 * Code to manage the game clocks.
14122 * In tournament play, black starts the clock and then white makes a move.
14123 * We give the human user a slight advantage if he is playing white---the
14124 * clocks don't run until he makes his first move, so it takes zero time.
14125 * Also, we don't account for network lag, so we could get out of sync
14126 * with GNU Chess's clock -- but then, referees are always right.
14129 static TimeMark tickStartTM;
14130 static long intendedTickLength;
14133 NextTickLength(timeRemaining)
14134 long timeRemaining;
14136 long nominalTickLength, nextTickLength;
14138 if (timeRemaining > 0L && timeRemaining <= 10000L)
14139 nominalTickLength = 100L;
14141 nominalTickLength = 1000L;
14142 nextTickLength = timeRemaining % nominalTickLength;
14143 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14145 return nextTickLength;
14148 /* Adjust clock one minute up or down */
14150 AdjustClock(Boolean which, int dir)
14152 if(which) blackTimeRemaining += 60000*dir;
14153 else whiteTimeRemaining += 60000*dir;
14154 DisplayBothClocks();
14157 /* Stop clocks and reset to a fresh time control */
14161 (void) StopClockTimer();
14162 if (appData.icsActive) {
14163 whiteTimeRemaining = blackTimeRemaining = 0;
14164 } else if (searchTime) {
14165 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14166 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14167 } else { /* [HGM] correct new time quote for time odds */
14168 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14169 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14171 if (whiteFlag || blackFlag) {
14173 whiteFlag = blackFlag = FALSE;
14175 DisplayBothClocks();
14178 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14180 /* Decrement running clock by amount of time that has passed */
14184 long timeRemaining;
14185 long lastTickLength, fudge;
14188 if (!appData.clockMode) return;
14189 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14193 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14195 /* Fudge if we woke up a little too soon */
14196 fudge = intendedTickLength - lastTickLength;
14197 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14199 if (WhiteOnMove(forwardMostMove)) {
14200 if(whiteNPS >= 0) lastTickLength = 0;
14201 timeRemaining = whiteTimeRemaining -= lastTickLength;
14202 DisplayWhiteClock(whiteTimeRemaining - fudge,
14203 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14205 if(blackNPS >= 0) lastTickLength = 0;
14206 timeRemaining = blackTimeRemaining -= lastTickLength;
14207 DisplayBlackClock(blackTimeRemaining - fudge,
14208 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14211 if (CheckFlags()) return;
14214 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14215 StartClockTimer(intendedTickLength);
14217 /* if the time remaining has fallen below the alarm threshold, sound the
14218 * alarm. if the alarm has sounded and (due to a takeback or time control
14219 * with increment) the time remaining has increased to a level above the
14220 * threshold, reset the alarm so it can sound again.
14223 if (appData.icsActive && appData.icsAlarm) {
14225 /* make sure we are dealing with the user's clock */
14226 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14227 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14230 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14231 alarmSounded = FALSE;
14232 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14234 alarmSounded = TRUE;
14240 /* A player has just moved, so stop the previously running
14241 clock and (if in clock mode) start the other one.
14242 We redisplay both clocks in case we're in ICS mode, because
14243 ICS gives us an update to both clocks after every move.
14244 Note that this routine is called *after* forwardMostMove
14245 is updated, so the last fractional tick must be subtracted
14246 from the color that is *not* on move now.
14249 SwitchClocks(int newMoveNr)
14251 long lastTickLength;
14253 int flagged = FALSE;
14257 if (StopClockTimer() && appData.clockMode) {
14258 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14259 if (!WhiteOnMove(forwardMostMove)) {
14260 if(blackNPS >= 0) lastTickLength = 0;
14261 blackTimeRemaining -= lastTickLength;
14262 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14263 // if(pvInfoList[forwardMostMove-1].time == -1)
14264 pvInfoList[forwardMostMove-1].time = // use GUI time
14265 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14267 if(whiteNPS >= 0) lastTickLength = 0;
14268 whiteTimeRemaining -= lastTickLength;
14269 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14270 // if(pvInfoList[forwardMostMove-1].time == -1)
14271 pvInfoList[forwardMostMove-1].time =
14272 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14274 flagged = CheckFlags();
14276 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14277 CheckTimeControl();
14279 if (flagged || !appData.clockMode) return;
14281 switch (gameMode) {
14282 case MachinePlaysBlack:
14283 case MachinePlaysWhite:
14284 case BeginningOfGame:
14285 if (pausing) return;
14289 case PlayFromGameFile:
14297 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14298 if(WhiteOnMove(forwardMostMove))
14299 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14300 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14304 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14305 whiteTimeRemaining : blackTimeRemaining);
14306 StartClockTimer(intendedTickLength);
14310 /* Stop both clocks */
14314 long lastTickLength;
14317 if (!StopClockTimer()) return;
14318 if (!appData.clockMode) return;
14322 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14323 if (WhiteOnMove(forwardMostMove)) {
14324 if(whiteNPS >= 0) lastTickLength = 0;
14325 whiteTimeRemaining -= lastTickLength;
14326 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14328 if(blackNPS >= 0) lastTickLength = 0;
14329 blackTimeRemaining -= lastTickLength;
14330 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14335 /* Start clock of player on move. Time may have been reset, so
14336 if clock is already running, stop and restart it. */
14340 (void) StopClockTimer(); /* in case it was running already */
14341 DisplayBothClocks();
14342 if (CheckFlags()) return;
14344 if (!appData.clockMode) return;
14345 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14347 GetTimeMark(&tickStartTM);
14348 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14349 whiteTimeRemaining : blackTimeRemaining);
14351 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14352 whiteNPS = blackNPS = -1;
14353 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14354 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14355 whiteNPS = first.nps;
14356 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14357 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14358 blackNPS = first.nps;
14359 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14360 whiteNPS = second.nps;
14361 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14362 blackNPS = second.nps;
14363 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14365 StartClockTimer(intendedTickLength);
14372 long second, minute, hour, day;
14374 static char buf[32];
14376 if (ms > 0 && ms <= 9900) {
14377 /* convert milliseconds to tenths, rounding up */
14378 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14380 sprintf(buf, " %03.1f ", tenths/10.0);
14384 /* convert milliseconds to seconds, rounding up */
14385 /* use floating point to avoid strangeness of integer division
14386 with negative dividends on many machines */
14387 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14394 day = second / (60 * 60 * 24);
14395 second = second % (60 * 60 * 24);
14396 hour = second / (60 * 60);
14397 second = second % (60 * 60);
14398 minute = second / 60;
14399 second = second % 60;
14402 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14403 sign, day, hour, minute, second);
14405 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14407 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14414 * This is necessary because some C libraries aren't ANSI C compliant yet.
14417 StrStr(string, match)
14418 char *string, *match;
14422 length = strlen(match);
14424 for (i = strlen(string) - length; i >= 0; i--, string++)
14425 if (!strncmp(match, string, length))
14432 StrCaseStr(string, match)
14433 char *string, *match;
14437 length = strlen(match);
14439 for (i = strlen(string) - length; i >= 0; i--, string++) {
14440 for (j = 0; j < length; j++) {
14441 if (ToLower(match[j]) != ToLower(string[j]))
14444 if (j == length) return string;
14458 c1 = ToLower(*s1++);
14459 c2 = ToLower(*s2++);
14460 if (c1 > c2) return 1;
14461 if (c1 < c2) return -1;
14462 if (c1 == NULLCHAR) return 0;
14471 return isupper(c) ? tolower(c) : c;
14479 return islower(c) ? toupper(c) : c;
14481 #endif /* !_amigados */
14489 if ((ret = (char *) malloc(strlen(s) + 1))) {
14496 StrSavePtr(s, savePtr)
14497 char *s, **savePtr;
14502 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14503 strcpy(*savePtr, s);
14515 clock = time((time_t *)NULL);
14516 tm = localtime(&clock);
14517 sprintf(buf, "%04d.%02d.%02d",
14518 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14519 return StrSave(buf);
14524 PositionToFEN(move, overrideCastling)
14526 char *overrideCastling;
14528 int i, j, fromX, fromY, toX, toY;
14535 whiteToPlay = (gameMode == EditPosition) ?
14536 !blackPlaysFirst : (move % 2 == 0);
14539 /* Piece placement data */
14540 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14542 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14543 if (boards[move][i][j] == EmptySquare) {
14545 } else { ChessSquare piece = boards[move][i][j];
14546 if (emptycount > 0) {
14547 if(emptycount<10) /* [HGM] can be >= 10 */
14548 *p++ = '0' + emptycount;
14549 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14552 if(PieceToChar(piece) == '+') {
14553 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14555 piece = (ChessSquare)(DEMOTED piece);
14557 *p++ = PieceToChar(piece);
14559 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14560 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14565 if (emptycount > 0) {
14566 if(emptycount<10) /* [HGM] can be >= 10 */
14567 *p++ = '0' + emptycount;
14568 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14575 /* [HGM] print Crazyhouse or Shogi holdings */
14576 if( gameInfo.holdingsWidth ) {
14577 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14579 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14580 piece = boards[move][i][BOARD_WIDTH-1];
14581 if( piece != EmptySquare )
14582 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14583 *p++ = PieceToChar(piece);
14585 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14586 piece = boards[move][BOARD_HEIGHT-i-1][0];
14587 if( piece != EmptySquare )
14588 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14589 *p++ = PieceToChar(piece);
14592 if( q == p ) *p++ = '-';
14598 *p++ = whiteToPlay ? 'w' : 'b';
14601 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14602 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14604 if(nrCastlingRights) {
14606 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14607 /* [HGM] write directly from rights */
14608 if(boards[move][CASTLING][2] != NoRights &&
14609 boards[move][CASTLING][0] != NoRights )
14610 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14611 if(boards[move][CASTLING][2] != NoRights &&
14612 boards[move][CASTLING][1] != NoRights )
14613 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14614 if(boards[move][CASTLING][5] != NoRights &&
14615 boards[move][CASTLING][3] != NoRights )
14616 *p++ = boards[move][CASTLING][3] + AAA;
14617 if(boards[move][CASTLING][5] != NoRights &&
14618 boards[move][CASTLING][4] != NoRights )
14619 *p++ = boards[move][CASTLING][4] + AAA;
14622 /* [HGM] write true castling rights */
14623 if( nrCastlingRights == 6 ) {
14624 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14625 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14626 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14627 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14628 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14629 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14630 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14631 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14634 if (q == p) *p++ = '-'; /* No castling rights */
14638 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14639 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14640 /* En passant target square */
14641 if (move > backwardMostMove) {
14642 fromX = moveList[move - 1][0] - AAA;
14643 fromY = moveList[move - 1][1] - ONE;
14644 toX = moveList[move - 1][2] - AAA;
14645 toY = moveList[move - 1][3] - ONE;
14646 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14647 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14648 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14650 /* 2-square pawn move just happened */
14652 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14656 } else if(move == backwardMostMove) {
14657 // [HGM] perhaps we should always do it like this, and forget the above?
14658 if((signed char)boards[move][EP_STATUS] >= 0) {
14659 *p++ = boards[move][EP_STATUS] + AAA;
14660 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14671 /* [HGM] find reversible plies */
14672 { int i = 0, j=move;
14674 if (appData.debugMode) { int k;
14675 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14676 for(k=backwardMostMove; k<=forwardMostMove; k++)
14677 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14681 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14682 if( j == backwardMostMove ) i += initialRulePlies;
14683 sprintf(p, "%d ", i);
14684 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14686 /* Fullmove number */
14687 sprintf(p, "%d", (move / 2) + 1);
14689 return StrSave(buf);
14693 ParseFEN(board, blackPlaysFirst, fen)
14695 int *blackPlaysFirst;
14705 /* [HGM] by default clear Crazyhouse holdings, if present */
14706 if(gameInfo.holdingsWidth) {
14707 for(i=0; i<BOARD_HEIGHT; i++) {
14708 board[i][0] = EmptySquare; /* black holdings */
14709 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14710 board[i][1] = (ChessSquare) 0; /* black counts */
14711 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14715 /* Piece placement data */
14716 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14719 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14720 if (*p == '/') p++;
14721 emptycount = gameInfo.boardWidth - j;
14722 while (emptycount--)
14723 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14725 #if(BOARD_FILES >= 10)
14726 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14727 p++; emptycount=10;
14728 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14729 while (emptycount--)
14730 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14732 } else if (isdigit(*p)) {
14733 emptycount = *p++ - '0';
14734 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14735 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14736 while (emptycount--)
14737 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14738 } else if (*p == '+' || isalpha(*p)) {
14739 if (j >= gameInfo.boardWidth) return FALSE;
14741 piece = CharToPiece(*++p);
14742 if(piece == EmptySquare) return FALSE; /* unknown piece */
14743 piece = (ChessSquare) (PROMOTED piece ); p++;
14744 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14745 } else piece = CharToPiece(*p++);
14747 if(piece==EmptySquare) return FALSE; /* unknown piece */
14748 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14749 piece = (ChessSquare) (PROMOTED piece);
14750 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14753 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14759 while (*p == '/' || *p == ' ') p++;
14761 /* [HGM] look for Crazyhouse holdings here */
14762 while(*p==' ') p++;
14763 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14765 if(*p == '-' ) *p++; /* empty holdings */ else {
14766 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14767 /* if we would allow FEN reading to set board size, we would */
14768 /* have to add holdings and shift the board read so far here */
14769 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14771 if((int) piece >= (int) BlackPawn ) {
14772 i = (int)piece - (int)BlackPawn;
14773 i = PieceToNumber((ChessSquare)i);
14774 if( i >= gameInfo.holdingsSize ) return FALSE;
14775 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14776 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14778 i = (int)piece - (int)WhitePawn;
14779 i = PieceToNumber((ChessSquare)i);
14780 if( i >= gameInfo.holdingsSize ) return FALSE;
14781 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14782 board[i][BOARD_WIDTH-2]++; /* black holdings */
14786 if(*p == ']') *p++;
14789 while(*p == ' ') p++;
14794 *blackPlaysFirst = FALSE;
14797 *blackPlaysFirst = TRUE;
14803 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14804 /* return the extra info in global variiables */
14806 /* set defaults in case FEN is incomplete */
14807 board[EP_STATUS] = EP_UNKNOWN;
14808 for(i=0; i<nrCastlingRights; i++ ) {
14809 board[CASTLING][i] =
14810 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14811 } /* assume possible unless obviously impossible */
14812 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14813 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14814 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14815 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14816 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14817 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14818 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14819 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14822 while(*p==' ') p++;
14823 if(nrCastlingRights) {
14824 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14825 /* castling indicator present, so default becomes no castlings */
14826 for(i=0; i<nrCastlingRights; i++ ) {
14827 board[CASTLING][i] = NoRights;
14830 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14831 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14832 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14833 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14834 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14836 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14837 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14838 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14840 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14841 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14842 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14843 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14844 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14845 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14848 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14849 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14850 board[CASTLING][2] = whiteKingFile;
14853 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14854 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14855 board[CASTLING][2] = whiteKingFile;
14858 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14859 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14860 board[CASTLING][5] = blackKingFile;
14863 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14864 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14865 board[CASTLING][5] = blackKingFile;
14868 default: /* FRC castlings */
14869 if(c >= 'a') { /* black rights */
14870 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14871 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14872 if(i == BOARD_RGHT) break;
14873 board[CASTLING][5] = i;
14875 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14876 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14878 board[CASTLING][3] = c;
14880 board[CASTLING][4] = c;
14881 } else { /* white rights */
14882 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14883 if(board[0][i] == WhiteKing) break;
14884 if(i == BOARD_RGHT) break;
14885 board[CASTLING][2] = i;
14886 c -= AAA - 'a' + 'A';
14887 if(board[0][c] >= WhiteKing) break;
14889 board[CASTLING][0] = c;
14891 board[CASTLING][1] = c;
14895 for(i=0; i<nrCastlingRights; i++)
14896 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14897 if (appData.debugMode) {
14898 fprintf(debugFP, "FEN castling rights:");
14899 for(i=0; i<nrCastlingRights; i++)
14900 fprintf(debugFP, " %d", board[CASTLING][i]);
14901 fprintf(debugFP, "\n");
14904 while(*p==' ') p++;
14907 /* read e.p. field in games that know e.p. capture */
14908 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14909 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14911 p++; board[EP_STATUS] = EP_NONE;
14913 char c = *p++ - AAA;
14915 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14916 if(*p >= '0' && *p <='9') *p++;
14917 board[EP_STATUS] = c;
14922 if(sscanf(p, "%d", &i) == 1) {
14923 FENrulePlies = i; /* 50-move ply counter */
14924 /* (The move number is still ignored) */
14931 EditPositionPasteFEN(char *fen)
14934 Board initial_position;
14936 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14937 DisplayError(_("Bad FEN position in clipboard"), 0);
14940 int savedBlackPlaysFirst = blackPlaysFirst;
14941 EditPositionEvent();
14942 blackPlaysFirst = savedBlackPlaysFirst;
14943 CopyBoard(boards[0], initial_position);
14944 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14945 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14946 DisplayBothClocks();
14947 DrawPosition(FALSE, boards[currentMove]);
14952 static char cseq[12] = "\\ ";
14954 Boolean set_cont_sequence(char *new_seq)
14959 // handle bad attempts to set the sequence
14961 return 0; // acceptable error - no debug
14963 len = strlen(new_seq);
14964 ret = (len > 0) && (len < sizeof(cseq));
14966 strcpy(cseq, new_seq);
14967 else if (appData.debugMode)
14968 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14973 reformat a source message so words don't cross the width boundary. internal
14974 newlines are not removed. returns the wrapped size (no null character unless
14975 included in source message). If dest is NULL, only calculate the size required
14976 for the dest buffer. lp argument indicats line position upon entry, and it's
14977 passed back upon exit.
14979 int wrap(char *dest, char *src, int count, int width, int *lp)
14981 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14983 cseq_len = strlen(cseq);
14984 old_line = line = *lp;
14985 ansi = len = clen = 0;
14987 for (i=0; i < count; i++)
14989 if (src[i] == '\033')
14992 // if we hit the width, back up
14993 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14995 // store i & len in case the word is too long
14996 old_i = i, old_len = len;
14998 // find the end of the last word
14999 while (i && src[i] != ' ' && src[i] != '\n')
15005 // word too long? restore i & len before splitting it
15006 if ((old_i-i+clen) >= width)
15013 if (i && src[i-1] == ' ')
15016 if (src[i] != ' ' && src[i] != '\n')
15023 // now append the newline and continuation sequence
15028 strncpy(dest+len, cseq, cseq_len);
15036 dest[len] = src[i];
15040 if (src[i] == '\n')
15045 if (dest && appData.debugMode)
15047 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15048 count, width, line, len, *lp);
15049 show_bytes(debugFP, src, count);
15050 fprintf(debugFP, "\ndest: ");
15051 show_bytes(debugFP, dest, len);
15052 fprintf(debugFP, "\n");
15054 *lp = dest ? line : old_line;
15059 // [HGM] vari: routines for shelving variations
15062 PushTail(int firstMove, int lastMove)
15064 int i, j, nrMoves = lastMove - firstMove;
15066 if(appData.icsActive) { // only in local mode
15067 forwardMostMove = currentMove; // mimic old ICS behavior
15070 if(storedGames >= MAX_VARIATIONS-1) return;
15072 // push current tail of game on stack
15073 savedResult[storedGames] = gameInfo.result;
15074 savedDetails[storedGames] = gameInfo.resultDetails;
15075 gameInfo.resultDetails = NULL;
15076 savedFirst[storedGames] = firstMove;
15077 savedLast [storedGames] = lastMove;
15078 savedFramePtr[storedGames] = framePtr;
15079 framePtr -= nrMoves; // reserve space for the boards
15080 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15081 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15082 for(j=0; j<MOVE_LEN; j++)
15083 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15084 for(j=0; j<2*MOVE_LEN; j++)
15085 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15086 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15087 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15088 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15089 pvInfoList[firstMove+i-1].depth = 0;
15090 commentList[framePtr+i] = commentList[firstMove+i];
15091 commentList[firstMove+i] = NULL;
15095 forwardMostMove = firstMove; // truncate game so we can start variation
15096 if(storedGames == 1) GreyRevert(FALSE);
15100 PopTail(Boolean annotate)
15103 char buf[8000], moveBuf[20];
15105 if(appData.icsActive) return FALSE; // only in local mode
15106 if(!storedGames) return FALSE; // sanity
15107 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15110 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15111 nrMoves = savedLast[storedGames] - currentMove;
15114 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15115 else strcpy(buf, "(");
15116 for(i=currentMove; i<forwardMostMove; i++) {
15118 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15119 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15120 strcat(buf, moveBuf);
15121 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15122 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15126 for(i=1; i<=nrMoves; i++) { // copy last variation back
15127 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15128 for(j=0; j<MOVE_LEN; j++)
15129 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15130 for(j=0; j<2*MOVE_LEN; j++)
15131 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15132 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15133 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15134 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15135 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15136 commentList[currentMove+i] = commentList[framePtr+i];
15137 commentList[framePtr+i] = NULL;
15139 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15140 framePtr = savedFramePtr[storedGames];
15141 gameInfo.result = savedResult[storedGames];
15142 if(gameInfo.resultDetails != NULL) {
15143 free(gameInfo.resultDetails);
15145 gameInfo.resultDetails = savedDetails[storedGames];
15146 forwardMostMove = currentMove + nrMoves;
15147 if(storedGames == 0) GreyRevert(TRUE);
15153 { // remove all shelved variations
15155 for(i=0; i<storedGames; i++) {
15156 if(savedDetails[i])
15157 free(savedDetails[i]);
15158 savedDetails[i] = NULL;
15160 for(i=framePtr; i<MAX_MOVES; i++) {
15161 if(commentList[i]) free(commentList[i]);
15162 commentList[i] = NULL;
15164 framePtr = MAX_MOVES-1;
15169 LoadVariation(int index, char *text)
15170 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15171 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15172 int level = 0, move;
15174 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15175 // first find outermost bracketing variation
15176 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15177 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15178 if(*p == '{') wait = '}'; else
15179 if(*p == '[') wait = ']'; else
15180 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15181 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15183 if(*p == wait) wait = NULLCHAR; // closing ]} found
15186 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15187 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15188 end[1] = NULLCHAR; // clip off comment beyond variation
15189 ToNrEvent(currentMove-1);
15190 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15191 // kludge: use ParsePV() to append variation to game
15192 move = currentMove;
15193 ParsePV(start, TRUE);
15194 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15195 ClearPremoveHighlights();
15197 ToNrEvent(currentMove+1);