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);
1110 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1111 free(programVersion);
1112 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1113 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1116 if (appData.icsActive) {
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
1123 if (*appData.icsCommPort != NULLCHAR) {
1124 sprintf(buf, _("Could not open comm port %s"),
1125 appData.icsCommPort);
1127 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128 appData.icsHost, appData.icsPort);
1130 DisplayFatalError(buf, err, 1);
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1139 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1140 } else if (appData.noChessProgram) {
1146 if (*appData.cmailGameName != NULLCHAR) {
1148 OpenLoopback(&cmailPR);
1150 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154 DisplayMessage("", "");
1155 if (StrCaseCmp(appData.initialMode, "") == 0) {
1156 initialMode = BeginningOfGame;
1157 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158 initialMode = TwoMachinesPlay;
1159 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160 initialMode = AnalyzeFile;
1161 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162 initialMode = AnalyzeMode;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164 initialMode = MachinePlaysWhite;
1165 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166 initialMode = MachinePlaysBlack;
1167 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168 initialMode = EditGame;
1169 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170 initialMode = EditPosition;
1171 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172 initialMode = Training;
1174 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175 DisplayFatalError(buf, 0, 2);
1179 if (appData.matchMode) {
1180 /* Set up machine vs. machine match */
1181 if (appData.noChessProgram) {
1182 DisplayFatalError(_("Can't have a match with no chess programs"),
1188 if (*appData.loadGameFile != NULLCHAR) {
1189 int index = appData.loadGameIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameFile, FALSE)) {
1194 DisplayFatalError(_("Bad game file"), 0, 1);
1197 } else if (*appData.loadPositionFile != NULLCHAR) {
1198 int index = appData.loadPositionIndex; // [HGM] autoinc
1199 if(index<0) lastIndex = index = 1;
1200 if (!LoadPositionFromFile(appData.loadPositionFile,
1202 appData.loadPositionFile)) {
1203 DisplayFatalError(_("Bad position file"), 0, 1);
1208 } else if (*appData.cmailGameName != NULLCHAR) {
1209 /* Set up cmail mode */
1210 ReloadCmailMsgEvent(TRUE);
1212 /* Set up other modes */
1213 if (initialMode == AnalyzeFile) {
1214 if (*appData.loadGameFile == NULLCHAR) {
1215 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219 if (*appData.loadGameFile != NULLCHAR) {
1220 (void) LoadGameFromFile(appData.loadGameFile,
1221 appData.loadGameIndex,
1222 appData.loadGameFile, TRUE);
1223 } else if (*appData.loadPositionFile != NULLCHAR) {
1224 (void) LoadPositionFromFile(appData.loadPositionFile,
1225 appData.loadPositionIndex,
1226 appData.loadPositionFile);
1227 /* [HGM] try to make self-starting even after FEN load */
1228 /* to allow automatic setup of fairy variants with wtm */
1229 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230 gameMode = BeginningOfGame;
1231 setboardSpoiledMachineBlack = 1;
1233 /* [HGM] loadPos: make that every new game uses the setup */
1234 /* from file as long as we do not switch variant */
1235 if(!blackPlaysFirst) {
1236 startedFromPositionFile = TRUE;
1237 CopyBoard(filePosition, boards[0]);
1240 if (initialMode == AnalyzeMode) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1250 } else if (initialMode == AnalyzeFile) {
1251 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1252 ShowThinkingEvent();
1254 AnalysisPeriodicEvent(1);
1255 } else if (initialMode == MachinePlaysWhite) {
1256 if (appData.noChessProgram) {
1257 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1261 if (appData.icsActive) {
1262 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1266 MachineWhiteEvent();
1267 } else if (initialMode == MachinePlaysBlack) {
1268 if (appData.noChessProgram) {
1269 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1273 if (appData.icsActive) {
1274 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1278 MachineBlackEvent();
1279 } else if (initialMode == TwoMachinesPlay) {
1280 if (appData.noChessProgram) {
1281 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1285 if (appData.icsActive) {
1286 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1291 } else if (initialMode == EditGame) {
1293 } else if (initialMode == EditPosition) {
1294 EditPositionEvent();
1295 } else if (initialMode == Training) {
1296 if (*appData.loadGameFile == NULLCHAR) {
1297 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1306 * Establish will establish a contact to a remote host.port.
1307 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1308 * used to talk to the host.
1309 * Returns 0 if okay, error code if not.
1316 if (*appData.icsCommPort != NULLCHAR) {
1317 /* Talk to the host through a serial comm port */
1318 return OpenCommPort(appData.icsCommPort, &icsPR);
1320 } else if (*appData.gateway != NULLCHAR) {
1321 if (*appData.remoteShell == NULLCHAR) {
1322 /* Use the rcmd protocol to run telnet program on a gateway host */
1323 snprintf(buf, sizeof(buf), "%s %s %s",
1324 appData.telnetProgram, appData.icsHost, appData.icsPort);
1325 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1328 /* Use the rsh program to run telnet program on a gateway host */
1329 if (*appData.remoteUser == NULLCHAR) {
1330 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1331 appData.gateway, appData.telnetProgram,
1332 appData.icsHost, appData.icsPort);
1334 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1335 appData.remoteShell, appData.gateway,
1336 appData.remoteUser, appData.telnetProgram,
1337 appData.icsHost, appData.icsPort);
1339 return StartChildProcess(buf, "", &icsPR);
1342 } else if (appData.useTelnet) {
1343 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1346 /* TCP socket interface differs somewhat between
1347 Unix and NT; handle details in the front end.
1349 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1354 show_bytes(fp, buf, count)
1360 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1361 fprintf(fp, "\\%03o", *buf & 0xff);
1370 /* Returns an errno value */
1372 OutputMaybeTelnet(pr, message, count, outError)
1378 char buf[8192], *p, *q, *buflim;
1379 int left, newcount, outcount;
1381 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1382 *appData.gateway != NULLCHAR) {
1383 if (appData.debugMode) {
1384 fprintf(debugFP, ">ICS: ");
1385 show_bytes(debugFP, message, count);
1386 fprintf(debugFP, "\n");
1388 return OutputToProcess(pr, message, count, outError);
1391 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1398 if (appData.debugMode) {
1399 fprintf(debugFP, ">ICS: ");
1400 show_bytes(debugFP, buf, newcount);
1401 fprintf(debugFP, "\n");
1403 outcount = OutputToProcess(pr, buf, newcount, outError);
1404 if (outcount < newcount) return -1; /* to be sure */
1411 } else if (((unsigned char) *p) == TN_IAC) {
1412 *q++ = (char) TN_IAC;
1419 if (appData.debugMode) {
1420 fprintf(debugFP, ">ICS: ");
1421 show_bytes(debugFP, buf, newcount);
1422 fprintf(debugFP, "\n");
1424 outcount = OutputToProcess(pr, buf, newcount, outError);
1425 if (outcount < newcount) return -1; /* to be sure */
1430 read_from_player(isr, closure, message, count, error)
1437 int outError, outCount;
1438 static int gotEof = 0;
1440 /* Pass data read from player on to ICS */
1443 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1444 if (outCount < count) {
1445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1447 } else if (count < 0) {
1448 RemoveInputSource(isr);
1449 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1450 } else if (gotEof++ > 0) {
1451 RemoveInputSource(isr);
1452 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1458 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1459 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1460 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1465 /* added routine for printf style output to ics */
1466 void ics_printf(char *format, ...)
1468 char buffer[MSG_SIZ];
1471 va_start(args, format);
1472 vsnprintf(buffer, sizeof(buffer), format, args);
1473 buffer[sizeof(buffer)-1] = '\0';
1482 int count, outCount, outError;
1484 if (icsPR == NULL) return;
1487 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1488 if (outCount < count) {
1489 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1493 /* This is used for sending logon scripts to the ICS. Sending
1494 without a delay causes problems when using timestamp on ICC
1495 (at least on my machine). */
1497 SendToICSDelayed(s,msdelay)
1501 int count, outCount, outError;
1503 if (icsPR == NULL) return;
1506 if (appData.debugMode) {
1507 fprintf(debugFP, ">ICS: ");
1508 show_bytes(debugFP, s, count);
1509 fprintf(debugFP, "\n");
1511 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1513 if (outCount < count) {
1514 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1519 /* Remove all highlighting escape sequences in s
1520 Also deletes any suffix starting with '('
1523 StripHighlightAndTitle(s)
1526 static char retbuf[MSG_SIZ];
1529 while (*s != NULLCHAR) {
1530 while (*s == '\033') {
1531 while (*s != NULLCHAR && !isalpha(*s)) s++;
1532 if (*s != NULLCHAR) s++;
1534 while (*s != NULLCHAR && *s != '\033') {
1535 if (*s == '(' || *s == '[') {
1546 /* Remove all highlighting escape sequences in s */
1551 static char retbuf[MSG_SIZ];
1554 while (*s != NULLCHAR) {
1555 while (*s == '\033') {
1556 while (*s != NULLCHAR && !isalpha(*s)) s++;
1557 if (*s != NULLCHAR) s++;
1559 while (*s != NULLCHAR && *s != '\033') {
1567 char *variantNames[] = VARIANT_NAMES;
1572 return variantNames[v];
1576 /* Identify a variant from the strings the chess servers use or the
1577 PGN Variant tag names we use. */
1584 VariantClass v = VariantNormal;
1585 int i, found = FALSE;
1590 /* [HGM] skip over optional board-size prefixes */
1591 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1592 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1593 while( *e++ != '_');
1596 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1600 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1601 if (StrCaseStr(e, variantNames[i])) {
1602 v = (VariantClass) i;
1609 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1610 || StrCaseStr(e, "wild/fr")
1611 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1612 v = VariantFischeRandom;
1613 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1614 (i = 1, p = StrCaseStr(e, "w"))) {
1616 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1623 case 0: /* FICS only, actually */
1625 /* Castling legal even if K starts on d-file */
1626 v = VariantWildCastle;
1631 /* Castling illegal even if K & R happen to start in
1632 normal positions. */
1633 v = VariantNoCastle;
1646 /* Castling legal iff K & R start in normal positions */
1652 /* Special wilds for position setup; unclear what to do here */
1653 v = VariantLoadable;
1656 /* Bizarre ICC game */
1657 v = VariantTwoKings;
1660 v = VariantKriegspiel;
1666 v = VariantFischeRandom;
1669 v = VariantCrazyhouse;
1672 v = VariantBughouse;
1678 /* Not quite the same as FICS suicide! */
1679 v = VariantGiveaway;
1685 v = VariantShatranj;
1688 /* Temporary names for future ICC types. The name *will* change in
1689 the next xboard/WinBoard release after ICC defines it. */
1727 v = VariantCapablanca;
1730 v = VariantKnightmate;
1736 v = VariantCylinder;
1742 v = VariantCapaRandom;
1745 v = VariantBerolina;
1757 /* Found "wild" or "w" in the string but no number;
1758 must assume it's normal chess. */
1762 sprintf(buf, _("Unknown wild type %d"), wnum);
1763 DisplayError(buf, 0);
1769 if (appData.debugMode) {
1770 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1771 e, wnum, VariantName(v));
1776 static int leftover_start = 0, leftover_len = 0;
1777 char star_match[STAR_MATCH_N][MSG_SIZ];
1779 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1780 advance *index beyond it, and set leftover_start to the new value of
1781 *index; else return FALSE. If pattern contains the character '*', it
1782 matches any sequence of characters not containing '\r', '\n', or the
1783 character following the '*' (if any), and the matched sequence(s) are
1784 copied into star_match.
1787 looking_at(buf, index, pattern)
1792 char *bufp = &buf[*index], *patternp = pattern;
1794 char *matchp = star_match[0];
1797 if (*patternp == NULLCHAR) {
1798 *index = leftover_start = bufp - buf;
1802 if (*bufp == NULLCHAR) return FALSE;
1803 if (*patternp == '*') {
1804 if (*bufp == *(patternp + 1)) {
1806 matchp = star_match[++star_count];
1810 } else if (*bufp == '\n' || *bufp == '\r') {
1812 if (*patternp == NULLCHAR)
1817 *matchp++ = *bufp++;
1821 if (*patternp != *bufp) return FALSE;
1828 SendToPlayer(data, length)
1832 int error, outCount;
1833 outCount = OutputToProcess(NoProc, data, length, &error);
1834 if (outCount < length) {
1835 DisplayFatalError(_("Error writing to display"), error, 1);
1840 PackHolding(packed, holding)
1852 switch (runlength) {
1863 sprintf(q, "%d", runlength);
1875 /* Telnet protocol requests from the front end */
1877 TelnetRequest(ddww, option)
1878 unsigned char ddww, option;
1880 unsigned char msg[3];
1881 int outCount, outError;
1883 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1885 if (appData.debugMode) {
1886 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1902 sprintf(buf1, "%d", ddww);
1911 sprintf(buf2, "%d", option);
1914 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1919 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1921 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1928 if (!appData.icsActive) return;
1929 TelnetRequest(TN_DO, TN_ECHO);
1935 if (!appData.icsActive) return;
1936 TelnetRequest(TN_DONT, TN_ECHO);
1940 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1942 /* put the holdings sent to us by the server on the board holdings area */
1943 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1947 if(gameInfo.holdingsWidth < 2) return;
1948 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1949 return; // prevent overwriting by pre-board holdings
1951 if( (int)lowestPiece >= BlackPawn ) {
1954 holdingsStartRow = BOARD_HEIGHT-1;
1957 holdingsColumn = BOARD_WIDTH-1;
1958 countsColumn = BOARD_WIDTH-2;
1959 holdingsStartRow = 0;
1963 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1964 board[i][holdingsColumn] = EmptySquare;
1965 board[i][countsColumn] = (ChessSquare) 0;
1967 while( (p=*holdings++) != NULLCHAR ) {
1968 piece = CharToPiece( ToUpper(p) );
1969 if(piece == EmptySquare) continue;
1970 /*j = (int) piece - (int) WhitePawn;*/
1971 j = PieceToNumber(piece);
1972 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1973 if(j < 0) continue; /* should not happen */
1974 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1975 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1976 board[holdingsStartRow+j*direction][countsColumn]++;
1982 VariantSwitch(Board board, VariantClass newVariant)
1984 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1987 startedFromPositionFile = FALSE;
1988 if(gameInfo.variant == newVariant) return;
1990 /* [HGM] This routine is called each time an assignment is made to
1991 * gameInfo.variant during a game, to make sure the board sizes
1992 * are set to match the new variant. If that means adding or deleting
1993 * holdings, we shift the playing board accordingly
1994 * This kludge is needed because in ICS observe mode, we get boards
1995 * of an ongoing game without knowing the variant, and learn about the
1996 * latter only later. This can be because of the move list we requested,
1997 * in which case the game history is refilled from the beginning anyway,
1998 * but also when receiving holdings of a crazyhouse game. In the latter
1999 * case we want to add those holdings to the already received position.
2003 if (appData.debugMode) {
2004 fprintf(debugFP, "Switch board from %s to %s\n",
2005 VariantName(gameInfo.variant), VariantName(newVariant));
2006 setbuf(debugFP, NULL);
2008 shuffleOpenings = 0; /* [HGM] shuffle */
2009 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2013 newWidth = 9; newHeight = 9;
2014 gameInfo.holdingsSize = 7;
2015 case VariantBughouse:
2016 case VariantCrazyhouse:
2017 newHoldingsWidth = 2; break;
2021 newHoldingsWidth = 2;
2022 gameInfo.holdingsSize = 8;
2025 case VariantCapablanca:
2026 case VariantCapaRandom:
2029 newHoldingsWidth = gameInfo.holdingsSize = 0;
2032 if(newWidth != gameInfo.boardWidth ||
2033 newHeight != gameInfo.boardHeight ||
2034 newHoldingsWidth != gameInfo.holdingsWidth ) {
2036 /* shift position to new playing area, if needed */
2037 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2038 for(i=0; i<BOARD_HEIGHT; i++)
2039 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2040 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2042 for(i=0; i<newHeight; i++) {
2043 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2044 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2046 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2047 for(i=0; i<BOARD_HEIGHT; i++)
2048 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2049 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2052 gameInfo.boardWidth = newWidth;
2053 gameInfo.boardHeight = newHeight;
2054 gameInfo.holdingsWidth = newHoldingsWidth;
2055 gameInfo.variant = newVariant;
2056 InitDrawingSizes(-2, 0);
2057 } else gameInfo.variant = newVariant;
2058 CopyBoard(oldBoard, board); // remember correctly formatted board
2059 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2060 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2063 static int loggedOn = FALSE;
2065 /*-- Game start info cache: --*/
2067 char gs_kind[MSG_SIZ];
2068 static char player1Name[128] = "";
2069 static char player2Name[128] = "";
2070 static char cont_seq[] = "\n\\ ";
2071 static int player1Rating = -1;
2072 static int player2Rating = -1;
2073 /*----------------------------*/
2075 ColorClass curColor = ColorNormal;
2076 int suppressKibitz = 0;
2079 Boolean soughtPending = FALSE;
2080 Boolean seekGraphUp;
2081 #define MAX_SEEK_ADS 200
2083 char *seekAdList[MAX_SEEK_ADS];
2084 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2085 float tcList[MAX_SEEK_ADS];
2086 char colorList[MAX_SEEK_ADS];
2087 int nrOfSeekAds = 0;
2088 int minRating = 1010, maxRating = 2800;
2089 int hMargin = 10, vMargin = 20, h, w;
2090 extern int squareSize, lineGap;
2095 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2096 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2097 if(r < minRating+100 && r >=0 ) r = minRating+100;
2098 if(r > maxRating) r = maxRating;
2099 if(tc < 1.) tc = 1.;
2100 if(tc > 95.) tc = 95.;
2101 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2102 y = ((double)r - minRating)/(maxRating - minRating)
2103 * (h-vMargin-squareSize/8-1) + vMargin;
2104 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2105 if(strstr(seekAdList[i], " u ")) color = 1;
2106 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2107 !strstr(seekAdList[i], "bullet") &&
2108 !strstr(seekAdList[i], "blitz") &&
2109 !strstr(seekAdList[i], "standard") ) color = 2;
2110 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2111 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2115 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2117 char buf[MSG_SIZ], *ext = "";
2118 VariantClass v = StringToVariant(type);
2119 if(strstr(type, "wild")) {
2120 ext = type + 4; // append wild number
2121 if(v == VariantFischeRandom) type = "chess960"; else
2122 if(v == VariantLoadable) type = "setup"; else
2123 type = VariantName(v);
2125 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2126 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2127 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2128 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2129 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2130 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2131 seekNrList[nrOfSeekAds] = nr;
2132 zList[nrOfSeekAds] = 0;
2133 seekAdList[nrOfSeekAds++] = StrSave(buf);
2134 if(plot) PlotSeekAd(nrOfSeekAds-1);
2141 int x = xList[i], y = yList[i], d=squareSize/4, k;
2142 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2143 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2144 // now replot every dot that overlapped
2145 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2146 int xx = xList[k], yy = yList[k];
2147 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2148 DrawSeekDot(xx, yy, colorList[k]);
2153 RemoveSeekAd(int nr)
2156 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2158 if(seekAdList[i]) free(seekAdList[i]);
2159 seekAdList[i] = seekAdList[--nrOfSeekAds];
2160 seekNrList[i] = seekNrList[nrOfSeekAds];
2161 ratingList[i] = ratingList[nrOfSeekAds];
2162 colorList[i] = colorList[nrOfSeekAds];
2163 tcList[i] = tcList[nrOfSeekAds];
2164 xList[i] = xList[nrOfSeekAds];
2165 yList[i] = yList[nrOfSeekAds];
2166 zList[i] = zList[nrOfSeekAds];
2167 seekAdList[nrOfSeekAds] = NULL;
2173 MatchSoughtLine(char *line)
2175 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2176 int nr, base, inc, u=0; char dummy;
2178 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2179 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2181 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2182 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2183 // match: compact and save the line
2184 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2193 if(!seekGraphUp) return FALSE;
2195 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2196 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2198 DrawSeekBackground(0, 0, w, h);
2199 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2200 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2201 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2202 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2204 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2207 sprintf(buf, "%d", i);
2208 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2211 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2212 for(i=1; i<100; i+=(i<10?1:5)) {
2213 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2214 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2215 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2217 sprintf(buf, "%d", i);
2218 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2221 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2225 int SeekGraphClick(ClickType click, int x, int y, int moving)
2227 static int lastDown = 0, displayed = 0, lastSecond;
2228 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2229 if(click == Release || moving) return FALSE;
2231 soughtPending = TRUE;
2232 SendToICS(ics_prefix);
2233 SendToICS("sought\n"); // should this be "sought all"?
2234 } else { // issue challenge based on clicked ad
2235 int dist = 10000; int i, closest = 0, second = 0;
2236 for(i=0; i<nrOfSeekAds; i++) {
2237 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2238 if(d < dist) { dist = d; closest = i; }
2239 second += (d - zList[i] < 120); // count in-range ads
2240 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2244 second = (second > 1);
2245 if(displayed != closest || second != lastSecond) {
2246 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2247 lastSecond = second; displayed = closest;
2249 if(click == Press) {
2250 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2253 } // on press 'hit', only show info
2254 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2255 sprintf(buf, "play %d\n", seekNrList[closest]);
2256 SendToICS(ics_prefix);
2258 return TRUE; // let incoming board of started game pop down the graph
2259 } else if(click == Release) { // release 'miss' is ignored
2260 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2261 if(moving == 2) { // right up-click
2262 nrOfSeekAds = 0; // refresh graph
2263 soughtPending = TRUE;
2264 SendToICS(ics_prefix);
2265 SendToICS("sought\n"); // should this be "sought all"?
2268 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2269 // press miss or release hit 'pop down' seek graph
2270 seekGraphUp = FALSE;
2271 DrawPosition(TRUE, NULL);
2277 read_from_ics(isr, closure, data, count, error)
2284 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2285 #define STARTED_NONE 0
2286 #define STARTED_MOVES 1
2287 #define STARTED_BOARD 2
2288 #define STARTED_OBSERVE 3
2289 #define STARTED_HOLDINGS 4
2290 #define STARTED_CHATTER 5
2291 #define STARTED_COMMENT 6
2292 #define STARTED_MOVES_NOHIDE 7
2294 static int started = STARTED_NONE;
2295 static char parse[20000];
2296 static int parse_pos = 0;
2297 static char buf[BUF_SIZE + 1];
2298 static int firstTime = TRUE, intfSet = FALSE;
2299 static ColorClass prevColor = ColorNormal;
2300 static int savingComment = FALSE;
2301 static int cmatch = 0; // continuation sequence match
2308 int backup; /* [DM] For zippy color lines */
2310 char talker[MSG_SIZ]; // [HGM] chat
2313 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2315 if (appData.debugMode) {
2317 fprintf(debugFP, "<ICS: ");
2318 show_bytes(debugFP, data, count);
2319 fprintf(debugFP, "\n");
2323 if (appData.debugMode) { int f = forwardMostMove;
2324 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2325 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2326 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2329 /* If last read ended with a partial line that we couldn't parse,
2330 prepend it to the new read and try again. */
2331 if (leftover_len > 0) {
2332 for (i=0; i<leftover_len; i++)
2333 buf[i] = buf[leftover_start + i];
2336 /* copy new characters into the buffer */
2337 bp = buf + leftover_len;
2338 buf_len=leftover_len;
2339 for (i=0; i<count; i++)
2342 if (data[i] == '\r')
2345 // join lines split by ICS?
2346 if (!appData.noJoin)
2349 Joining just consists of finding matches against the
2350 continuation sequence, and discarding that sequence
2351 if found instead of copying it. So, until a match
2352 fails, there's nothing to do since it might be the
2353 complete sequence, and thus, something we don't want
2356 if (data[i] == cont_seq[cmatch])
2359 if (cmatch == strlen(cont_seq))
2361 cmatch = 0; // complete match. just reset the counter
2364 it's possible for the ICS to not include the space
2365 at the end of the last word, making our [correct]
2366 join operation fuse two separate words. the server
2367 does this when the space occurs at the width setting.
2369 if (!buf_len || buf[buf_len-1] != ' ')
2380 match failed, so we have to copy what matched before
2381 falling through and copying this character. In reality,
2382 this will only ever be just the newline character, but
2383 it doesn't hurt to be precise.
2385 strncpy(bp, cont_seq, cmatch);
2397 buf[buf_len] = NULLCHAR;
2398 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2403 while (i < buf_len) {
2404 /* Deal with part of the TELNET option negotiation
2405 protocol. We refuse to do anything beyond the
2406 defaults, except that we allow the WILL ECHO option,
2407 which ICS uses to turn off password echoing when we are
2408 directly connected to it. We reject this option
2409 if localLineEditing mode is on (always on in xboard)
2410 and we are talking to port 23, which might be a real
2411 telnet server that will try to keep WILL ECHO on permanently.
2413 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2414 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2415 unsigned char option;
2417 switch ((unsigned char) buf[++i]) {
2419 if (appData.debugMode)
2420 fprintf(debugFP, "\n<WILL ");
2421 switch (option = (unsigned char) buf[++i]) {
2423 if (appData.debugMode)
2424 fprintf(debugFP, "ECHO ");
2425 /* Reply only if this is a change, according
2426 to the protocol rules. */
2427 if (remoteEchoOption) break;
2428 if (appData.localLineEditing &&
2429 atoi(appData.icsPort) == TN_PORT) {
2430 TelnetRequest(TN_DONT, TN_ECHO);
2433 TelnetRequest(TN_DO, TN_ECHO);
2434 remoteEchoOption = TRUE;
2438 if (appData.debugMode)
2439 fprintf(debugFP, "%d ", option);
2440 /* Whatever this is, we don't want it. */
2441 TelnetRequest(TN_DONT, option);
2446 if (appData.debugMode)
2447 fprintf(debugFP, "\n<WONT ");
2448 switch (option = (unsigned char) buf[++i]) {
2450 if (appData.debugMode)
2451 fprintf(debugFP, "ECHO ");
2452 /* Reply only if this is a change, according
2453 to the protocol rules. */
2454 if (!remoteEchoOption) break;
2456 TelnetRequest(TN_DONT, TN_ECHO);
2457 remoteEchoOption = FALSE;
2460 if (appData.debugMode)
2461 fprintf(debugFP, "%d ", (unsigned char) option);
2462 /* Whatever this is, it must already be turned
2463 off, because we never agree to turn on
2464 anything non-default, so according to the
2465 protocol rules, we don't reply. */
2470 if (appData.debugMode)
2471 fprintf(debugFP, "\n<DO ");
2472 switch (option = (unsigned char) buf[++i]) {
2474 /* Whatever this is, we refuse to do it. */
2475 if (appData.debugMode)
2476 fprintf(debugFP, "%d ", option);
2477 TelnetRequest(TN_WONT, option);
2482 if (appData.debugMode)
2483 fprintf(debugFP, "\n<DONT ");
2484 switch (option = (unsigned char) buf[++i]) {
2486 if (appData.debugMode)
2487 fprintf(debugFP, "%d ", option);
2488 /* Whatever this is, we are already not doing
2489 it, because we never agree to do anything
2490 non-default, so according to the protocol
2491 rules, we don't reply. */
2496 if (appData.debugMode)
2497 fprintf(debugFP, "\n<IAC ");
2498 /* Doubled IAC; pass it through */
2502 if (appData.debugMode)
2503 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2504 /* Drop all other telnet commands on the floor */
2507 if (oldi > next_out)
2508 SendToPlayer(&buf[next_out], oldi - next_out);
2514 /* OK, this at least will *usually* work */
2515 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2519 if (loggedOn && !intfSet) {
2520 if (ics_type == ICS_ICC) {
2522 "/set-quietly interface %s\n/set-quietly style 12\n",
2524 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2525 strcat(str, "/set-2 51 1\n/set seek 1\n");
2526 } else if (ics_type == ICS_CHESSNET) {
2527 sprintf(str, "/style 12\n");
2529 strcpy(str, "alias $ @\n$set interface ");
2530 strcat(str, programVersion);
2531 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2532 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2533 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2535 strcat(str, "$iset nohighlight 1\n");
2537 strcat(str, "$iset lock 1\n$style 12\n");
2540 NotifyFrontendLogin();
2544 if (started == STARTED_COMMENT) {
2545 /* Accumulate characters in comment */
2546 parse[parse_pos++] = buf[i];
2547 if (buf[i] == '\n') {
2548 parse[parse_pos] = NULLCHAR;
2549 if(chattingPartner>=0) {
2551 sprintf(mess, "%s%s", talker, parse);
2552 OutputChatMessage(chattingPartner, mess);
2553 chattingPartner = -1;
2554 next_out = i+1; // [HGM] suppress printing in ICS window
2556 if(!suppressKibitz) // [HGM] kibitz
2557 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2558 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2559 int nrDigit = 0, nrAlph = 0, j;
2560 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2561 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2562 parse[parse_pos] = NULLCHAR;
2563 // try to be smart: if it does not look like search info, it should go to
2564 // ICS interaction window after all, not to engine-output window.
2565 for(j=0; j<parse_pos; j++) { // count letters and digits
2566 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2567 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2568 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2570 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2571 int depth=0; float score;
2572 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2573 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2574 pvInfoList[forwardMostMove-1].depth = depth;
2575 pvInfoList[forwardMostMove-1].score = 100*score;
2577 OutputKibitz(suppressKibitz, parse);
2580 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2581 SendToPlayer(tmp, strlen(tmp));
2583 next_out = i+1; // [HGM] suppress printing in ICS window
2585 started = STARTED_NONE;
2587 /* Don't match patterns against characters in comment */
2592 if (started == STARTED_CHATTER) {
2593 if (buf[i] != '\n') {
2594 /* Don't match patterns against characters in chatter */
2598 started = STARTED_NONE;
2599 if(suppressKibitz) next_out = i+1;
2602 /* Kludge to deal with rcmd protocol */
2603 if (firstTime && looking_at(buf, &i, "\001*")) {
2604 DisplayFatalError(&buf[1], 0, 1);
2610 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2613 if (appData.debugMode)
2614 fprintf(debugFP, "ics_type %d\n", ics_type);
2617 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2618 ics_type = ICS_FICS;
2620 if (appData.debugMode)
2621 fprintf(debugFP, "ics_type %d\n", ics_type);
2624 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2625 ics_type = ICS_CHESSNET;
2627 if (appData.debugMode)
2628 fprintf(debugFP, "ics_type %d\n", ics_type);
2633 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2634 looking_at(buf, &i, "Logging you in as \"*\"") ||
2635 looking_at(buf, &i, "will be \"*\""))) {
2636 strcpy(ics_handle, star_match[0]);
2640 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2642 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2643 DisplayIcsInteractionTitle(buf);
2644 have_set_title = TRUE;
2647 /* skip finger notes */
2648 if (started == STARTED_NONE &&
2649 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2650 (buf[i] == '1' && buf[i+1] == '0')) &&
2651 buf[i+2] == ':' && buf[i+3] == ' ') {
2652 started = STARTED_CHATTER;
2658 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2659 if(appData.seekGraph) {
2660 if(soughtPending && MatchSoughtLine(buf+i)) {
2661 i = strstr(buf+i, "rated") - buf;
2662 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2663 next_out = leftover_start = i;
2664 started = STARTED_CHATTER;
2665 suppressKibitz = TRUE;
2668 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2669 && looking_at(buf, &i, "* ads displayed")) {
2670 soughtPending = FALSE;
2675 if(appData.autoRefresh) {
2676 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2677 int s = (ics_type == ICS_ICC); // ICC format differs
2679 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2680 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2681 looking_at(buf, &i, "*% "); // eat prompt
2682 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2683 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2684 next_out = i; // suppress
2687 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2688 char *p = star_match[0];
2690 if(seekGraphUp) RemoveSeekAd(atoi(p));
2691 while(*p && *p++ != ' '); // next
2693 looking_at(buf, &i, "*% "); // eat prompt
2694 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2701 /* skip formula vars */
2702 if (started == STARTED_NONE &&
2703 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2704 started = STARTED_CHATTER;
2709 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2710 if (appData.autoKibitz && started == STARTED_NONE &&
2711 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2712 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2713 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2714 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2715 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2716 suppressKibitz = TRUE;
2717 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2719 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2720 && (gameMode == IcsPlayingWhite)) ||
2721 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2722 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2723 started = STARTED_CHATTER; // own kibitz we simply discard
2725 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2726 parse_pos = 0; parse[0] = NULLCHAR;
2727 savingComment = TRUE;
2728 suppressKibitz = gameMode != IcsObserving ? 2 :
2729 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2733 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2734 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2735 && atoi(star_match[0])) {
2736 // suppress the acknowledgements of our own autoKibitz
2738 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2739 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2740 SendToPlayer(star_match[0], strlen(star_match[0]));
2741 if(looking_at(buf, &i, "*% ")) // eat prompt
2742 suppressKibitz = FALSE;
2746 } // [HGM] kibitz: end of patch
2748 // [HGM] chat: intercept tells by users for which we have an open chat window
2750 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2751 looking_at(buf, &i, "* whispers:") ||
2752 looking_at(buf, &i, "* kibitzes:") ||
2753 looking_at(buf, &i, "* shouts:") ||
2754 looking_at(buf, &i, "* c-shouts:") ||
2755 looking_at(buf, &i, "--> * ") ||
2756 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2757 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2758 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2759 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2761 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2762 chattingPartner = -1;
2764 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2765 for(p=0; p<MAX_CHAT; p++) {
2766 if(channel == atoi(chatPartner[p])) {
2767 talker[0] = '['; strcat(talker, "] ");
2768 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2769 chattingPartner = p; break;
2772 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2773 for(p=0; p<MAX_CHAT; p++) {
2774 if(!strcmp("kibitzes", chatPartner[p])) {
2775 talker[0] = '['; strcat(talker, "] ");
2776 chattingPartner = p; break;
2779 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2780 for(p=0; p<MAX_CHAT; p++) {
2781 if(!strcmp("whispers", chatPartner[p])) {
2782 talker[0] = '['; strcat(talker, "] ");
2783 chattingPartner = p; break;
2786 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2787 if(buf[i-8] == '-' && buf[i-3] == 't')
2788 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2789 if(!strcmp("c-shouts", chatPartner[p])) {
2790 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2791 chattingPartner = p; break;
2794 if(chattingPartner < 0)
2795 for(p=0; p<MAX_CHAT; p++) {
2796 if(!strcmp("shouts", chatPartner[p])) {
2797 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2798 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2799 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2800 chattingPartner = p; break;
2804 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2805 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2806 talker[0] = 0; Colorize(ColorTell, FALSE);
2807 chattingPartner = p; break;
2809 if(chattingPartner<0) i = oldi; else {
2810 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2811 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2812 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2813 started = STARTED_COMMENT;
2814 parse_pos = 0; parse[0] = NULLCHAR;
2815 savingComment = 3 + chattingPartner; // counts as TRUE
2816 suppressKibitz = TRUE;
2819 } // [HGM] chat: end of patch
2821 if (appData.zippyTalk || appData.zippyPlay) {
2822 /* [DM] Backup address for color zippy lines */
2826 if (loggedOn == TRUE)
2827 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2828 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2830 if (ZippyControl(buf, &i) ||
2831 ZippyConverse(buf, &i) ||
2832 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2834 if (!appData.colorize) continue;
2838 } // [DM] 'else { ' deleted
2840 /* Regular tells and says */
2841 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2842 looking_at(buf, &i, "* (your partner) tells you: ") ||
2843 looking_at(buf, &i, "* says: ") ||
2844 /* Don't color "message" or "messages" output */
2845 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2846 looking_at(buf, &i, "*. * at *:*: ") ||
2847 looking_at(buf, &i, "--* (*:*): ") ||
2848 /* Message notifications (same color as tells) */
2849 looking_at(buf, &i, "* has left a message ") ||
2850 looking_at(buf, &i, "* just sent you a message:\n") ||
2851 /* Whispers and kibitzes */
2852 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2853 looking_at(buf, &i, "* kibitzes: ") ||
2855 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2857 if (tkind == 1 && strchr(star_match[0], ':')) {
2858 /* Avoid "tells you:" spoofs in channels */
2861 if (star_match[0][0] == NULLCHAR ||
2862 strchr(star_match[0], ' ') ||
2863 (tkind == 3 && strchr(star_match[1], ' '))) {
2864 /* Reject bogus matches */
2867 if (appData.colorize) {
2868 if (oldi > next_out) {
2869 SendToPlayer(&buf[next_out], oldi - next_out);
2874 Colorize(ColorTell, FALSE);
2875 curColor = ColorTell;
2878 Colorize(ColorKibitz, FALSE);
2879 curColor = ColorKibitz;
2882 p = strrchr(star_match[1], '(');
2889 Colorize(ColorChannel1, FALSE);
2890 curColor = ColorChannel1;
2892 Colorize(ColorChannel, FALSE);
2893 curColor = ColorChannel;
2897 curColor = ColorNormal;
2901 if (started == STARTED_NONE && appData.autoComment &&
2902 (gameMode == IcsObserving ||
2903 gameMode == IcsPlayingWhite ||
2904 gameMode == IcsPlayingBlack)) {
2905 parse_pos = i - oldi;
2906 memcpy(parse, &buf[oldi], parse_pos);
2907 parse[parse_pos] = NULLCHAR;
2908 started = STARTED_COMMENT;
2909 savingComment = TRUE;
2911 started = STARTED_CHATTER;
2912 savingComment = FALSE;
2919 if (looking_at(buf, &i, "* s-shouts: ") ||
2920 looking_at(buf, &i, "* c-shouts: ")) {
2921 if (appData.colorize) {
2922 if (oldi > next_out) {
2923 SendToPlayer(&buf[next_out], oldi - next_out);
2926 Colorize(ColorSShout, FALSE);
2927 curColor = ColorSShout;
2930 started = STARTED_CHATTER;
2934 if (looking_at(buf, &i, "--->")) {
2939 if (looking_at(buf, &i, "* shouts: ") ||
2940 looking_at(buf, &i, "--> ")) {
2941 if (appData.colorize) {
2942 if (oldi > next_out) {
2943 SendToPlayer(&buf[next_out], oldi - next_out);
2946 Colorize(ColorShout, FALSE);
2947 curColor = ColorShout;
2950 started = STARTED_CHATTER;
2954 if (looking_at( buf, &i, "Challenge:")) {
2955 if (appData.colorize) {
2956 if (oldi > next_out) {
2957 SendToPlayer(&buf[next_out], oldi - next_out);
2960 Colorize(ColorChallenge, FALSE);
2961 curColor = ColorChallenge;
2967 if (looking_at(buf, &i, "* offers you") ||
2968 looking_at(buf, &i, "* offers to be") ||
2969 looking_at(buf, &i, "* would like to") ||
2970 looking_at(buf, &i, "* requests to") ||
2971 looking_at(buf, &i, "Your opponent offers") ||
2972 looking_at(buf, &i, "Your opponent requests")) {
2974 if (appData.colorize) {
2975 if (oldi > next_out) {
2976 SendToPlayer(&buf[next_out], oldi - next_out);
2979 Colorize(ColorRequest, FALSE);
2980 curColor = ColorRequest;
2985 if (looking_at(buf, &i, "* (*) seeking")) {
2986 if (appData.colorize) {
2987 if (oldi > next_out) {
2988 SendToPlayer(&buf[next_out], oldi - next_out);
2991 Colorize(ColorSeek, FALSE);
2992 curColor = ColorSeek;
2997 if (looking_at(buf, &i, "\\ ")) {
2998 if (prevColor != ColorNormal) {
2999 if (oldi > next_out) {
3000 SendToPlayer(&buf[next_out], oldi - next_out);
3003 Colorize(prevColor, TRUE);
3004 curColor = prevColor;
3006 if (savingComment) {
3007 parse_pos = i - oldi;
3008 memcpy(parse, &buf[oldi], parse_pos);
3009 parse[parse_pos] = NULLCHAR;
3010 started = STARTED_COMMENT;
3011 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3012 chattingPartner = savingComment - 3; // kludge to remember the box
3014 started = STARTED_CHATTER;
3019 if (looking_at(buf, &i, "Black Strength :") ||
3020 looking_at(buf, &i, "<<< style 10 board >>>") ||
3021 looking_at(buf, &i, "<10>") ||
3022 looking_at(buf, &i, "#@#")) {
3023 /* Wrong board style */
3025 SendToICS(ics_prefix);
3026 SendToICS("set style 12\n");
3027 SendToICS(ics_prefix);
3028 SendToICS("refresh\n");
3032 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3034 have_sent_ICS_logon = 1;
3038 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3039 (looking_at(buf, &i, "\n<12> ") ||
3040 looking_at(buf, &i, "<12> "))) {
3042 if (oldi > next_out) {
3043 SendToPlayer(&buf[next_out], oldi - next_out);
3046 started = STARTED_BOARD;
3051 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3052 looking_at(buf, &i, "<b1> ")) {
3053 if (oldi > next_out) {
3054 SendToPlayer(&buf[next_out], oldi - next_out);
3057 started = STARTED_HOLDINGS;
3062 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3064 /* Header for a move list -- first line */
3066 switch (ics_getting_history) {
3070 case BeginningOfGame:
3071 /* User typed "moves" or "oldmoves" while we
3072 were idle. Pretend we asked for these
3073 moves and soak them up so user can step
3074 through them and/or save them.
3077 gameMode = IcsObserving;
3080 ics_getting_history = H_GOT_UNREQ_HEADER;
3082 case EditGame: /*?*/
3083 case EditPosition: /*?*/
3084 /* Should above feature work in these modes too? */
3085 /* For now it doesn't */
3086 ics_getting_history = H_GOT_UNWANTED_HEADER;
3089 ics_getting_history = H_GOT_UNWANTED_HEADER;
3094 /* Is this the right one? */
3095 if (gameInfo.white && gameInfo.black &&
3096 strcmp(gameInfo.white, star_match[0]) == 0 &&
3097 strcmp(gameInfo.black, star_match[2]) == 0) {
3099 ics_getting_history = H_GOT_REQ_HEADER;
3102 case H_GOT_REQ_HEADER:
3103 case H_GOT_UNREQ_HEADER:
3104 case H_GOT_UNWANTED_HEADER:
3105 case H_GETTING_MOVES:
3106 /* Should not happen */
3107 DisplayError(_("Error gathering move list: two headers"), 0);
3108 ics_getting_history = H_FALSE;
3112 /* Save player ratings into gameInfo if needed */
3113 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3114 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3115 (gameInfo.whiteRating == -1 ||
3116 gameInfo.blackRating == -1)) {
3118 gameInfo.whiteRating = string_to_rating(star_match[1]);
3119 gameInfo.blackRating = string_to_rating(star_match[3]);
3120 if (appData.debugMode)
3121 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3122 gameInfo.whiteRating, gameInfo.blackRating);
3127 if (looking_at(buf, &i,
3128 "* * match, initial time: * minute*, increment: * second")) {
3129 /* Header for a move list -- second line */
3130 /* Initial board will follow if this is a wild game */
3131 if (gameInfo.event != NULL) free(gameInfo.event);
3132 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3133 gameInfo.event = StrSave(str);
3134 /* [HGM] we switched variant. Translate boards if needed. */
3135 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3139 if (looking_at(buf, &i, "Move ")) {
3140 /* Beginning of a move list */
3141 switch (ics_getting_history) {
3143 /* Normally should not happen */
3144 /* Maybe user hit reset while we were parsing */
3147 /* Happens if we are ignoring a move list that is not
3148 * the one we just requested. Common if the user
3149 * tries to observe two games without turning off
3152 case H_GETTING_MOVES:
3153 /* Should not happen */
3154 DisplayError(_("Error gathering move list: nested"), 0);
3155 ics_getting_history = H_FALSE;
3157 case H_GOT_REQ_HEADER:
3158 ics_getting_history = H_GETTING_MOVES;
3159 started = STARTED_MOVES;
3161 if (oldi > next_out) {
3162 SendToPlayer(&buf[next_out], oldi - next_out);
3165 case H_GOT_UNREQ_HEADER:
3166 ics_getting_history = H_GETTING_MOVES;
3167 started = STARTED_MOVES_NOHIDE;
3170 case H_GOT_UNWANTED_HEADER:
3171 ics_getting_history = H_FALSE;
3177 if (looking_at(buf, &i, "% ") ||
3178 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3179 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3180 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3181 soughtPending = FALSE;
3185 if(suppressKibitz) next_out = i;
3186 savingComment = FALSE;
3190 case STARTED_MOVES_NOHIDE:
3191 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3192 parse[parse_pos + i - oldi] = NULLCHAR;
3193 ParseGameHistory(parse);
3195 if (appData.zippyPlay && first.initDone) {
3196 FeedMovesToProgram(&first, forwardMostMove);
3197 if (gameMode == IcsPlayingWhite) {
3198 if (WhiteOnMove(forwardMostMove)) {
3199 if (first.sendTime) {
3200 if (first.useColors) {
3201 SendToProgram("black\n", &first);
3203 SendTimeRemaining(&first, TRUE);
3205 if (first.useColors) {
3206 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3208 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3209 first.maybeThinking = TRUE;
3211 if (first.usePlayother) {
3212 if (first.sendTime) {
3213 SendTimeRemaining(&first, TRUE);
3215 SendToProgram("playother\n", &first);
3221 } else if (gameMode == IcsPlayingBlack) {
3222 if (!WhiteOnMove(forwardMostMove)) {
3223 if (first.sendTime) {
3224 if (first.useColors) {
3225 SendToProgram("white\n", &first);
3227 SendTimeRemaining(&first, FALSE);
3229 if (first.useColors) {
3230 SendToProgram("black\n", &first);
3232 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3233 first.maybeThinking = TRUE;
3235 if (first.usePlayother) {
3236 if (first.sendTime) {
3237 SendTimeRemaining(&first, FALSE);
3239 SendToProgram("playother\n", &first);
3248 if (gameMode == IcsObserving && ics_gamenum == -1) {
3249 /* Moves came from oldmoves or moves command
3250 while we weren't doing anything else.
3252 currentMove = forwardMostMove;
3253 ClearHighlights();/*!!could figure this out*/
3254 flipView = appData.flipView;
3255 DrawPosition(TRUE, boards[currentMove]);
3256 DisplayBothClocks();
3257 sprintf(str, "%s vs. %s",
3258 gameInfo.white, gameInfo.black);
3262 /* Moves were history of an active game */
3263 if (gameInfo.resultDetails != NULL) {
3264 free(gameInfo.resultDetails);
3265 gameInfo.resultDetails = NULL;
3268 HistorySet(parseList, backwardMostMove,
3269 forwardMostMove, currentMove-1);
3270 DisplayMove(currentMove - 1);
3271 if (started == STARTED_MOVES) next_out = i;
3272 started = STARTED_NONE;
3273 ics_getting_history = H_FALSE;
3276 case STARTED_OBSERVE:
3277 started = STARTED_NONE;
3278 SendToICS(ics_prefix);
3279 SendToICS("refresh\n");
3285 if(bookHit) { // [HGM] book: simulate book reply
3286 static char bookMove[MSG_SIZ]; // a bit generous?
3288 programStats.nodes = programStats.depth = programStats.time =
3289 programStats.score = programStats.got_only_move = 0;
3290 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3292 strcpy(bookMove, "move ");
3293 strcat(bookMove, bookHit);
3294 HandleMachineMove(bookMove, &first);
3299 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3300 started == STARTED_HOLDINGS ||
3301 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3302 /* Accumulate characters in move list or board */
3303 parse[parse_pos++] = buf[i];
3306 /* Start of game messages. Mostly we detect start of game
3307 when the first board image arrives. On some versions
3308 of the ICS, though, we need to do a "refresh" after starting
3309 to observe in order to get the current board right away. */
3310 if (looking_at(buf, &i, "Adding game * to observation list")) {
3311 started = STARTED_OBSERVE;
3315 /* Handle auto-observe */
3316 if (appData.autoObserve &&
3317 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3318 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3320 /* Choose the player that was highlighted, if any. */
3321 if (star_match[0][0] == '\033' ||
3322 star_match[1][0] != '\033') {
3323 player = star_match[0];
3325 player = star_match[2];
3327 sprintf(str, "%sobserve %s\n",
3328 ics_prefix, StripHighlightAndTitle(player));
3331 /* Save ratings from notify string */
3332 strcpy(player1Name, star_match[0]);
3333 player1Rating = string_to_rating(star_match[1]);
3334 strcpy(player2Name, star_match[2]);
3335 player2Rating = string_to_rating(star_match[3]);
3337 if (appData.debugMode)
3339 "Ratings from 'Game notification:' %s %d, %s %d\n",
3340 player1Name, player1Rating,
3341 player2Name, player2Rating);
3346 /* Deal with automatic examine mode after a game,
3347 and with IcsObserving -> IcsExamining transition */
3348 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3349 looking_at(buf, &i, "has made you an examiner of game *")) {
3351 int gamenum = atoi(star_match[0]);
3352 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3353 gamenum == ics_gamenum) {
3354 /* We were already playing or observing this game;
3355 no need to refetch history */
3356 gameMode = IcsExamining;
3358 pauseExamForwardMostMove = forwardMostMove;
3359 } else if (currentMove < forwardMostMove) {
3360 ForwardInner(forwardMostMove);
3363 /* I don't think this case really can happen */
3364 SendToICS(ics_prefix);
3365 SendToICS("refresh\n");
3370 /* Error messages */
3371 // if (ics_user_moved) {
3372 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3373 if (looking_at(buf, &i, "Illegal move") ||
3374 looking_at(buf, &i, "Not a legal move") ||
3375 looking_at(buf, &i, "Your king is in check") ||
3376 looking_at(buf, &i, "It isn't your turn") ||
3377 looking_at(buf, &i, "It is not your move")) {
3379 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3380 currentMove = forwardMostMove-1;
3381 DisplayMove(currentMove - 1); /* before DMError */
3382 DrawPosition(FALSE, boards[currentMove]);
3383 SwitchClocks(forwardMostMove-1); // [HGM] race
3384 DisplayBothClocks();
3386 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3392 if (looking_at(buf, &i, "still have time") ||
3393 looking_at(buf, &i, "not out of time") ||
3394 looking_at(buf, &i, "either player is out of time") ||
3395 looking_at(buf, &i, "has timeseal; checking")) {
3396 /* We must have called his flag a little too soon */
3397 whiteFlag = blackFlag = FALSE;
3401 if (looking_at(buf, &i, "added * seconds to") ||
3402 looking_at(buf, &i, "seconds were added to")) {
3403 /* Update the clocks */
3404 SendToICS(ics_prefix);
3405 SendToICS("refresh\n");
3409 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3410 ics_clock_paused = TRUE;
3415 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3416 ics_clock_paused = FALSE;
3421 /* Grab player ratings from the Creating: message.
3422 Note we have to check for the special case when
3423 the ICS inserts things like [white] or [black]. */
3424 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3425 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3427 0 player 1 name (not necessarily white)
3429 2 empty, white, or black (IGNORED)
3430 3 player 2 name (not necessarily black)
3433 The names/ratings are sorted out when the game
3434 actually starts (below).
3436 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3437 player1Rating = string_to_rating(star_match[1]);
3438 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3439 player2Rating = string_to_rating(star_match[4]);
3441 if (appData.debugMode)
3443 "Ratings from 'Creating:' %s %d, %s %d\n",
3444 player1Name, player1Rating,
3445 player2Name, player2Rating);
3450 /* Improved generic start/end-of-game messages */
3451 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3452 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3453 /* If tkind == 0: */
3454 /* star_match[0] is the game number */
3455 /* [1] is the white player's name */
3456 /* [2] is the black player's name */
3457 /* For end-of-game: */
3458 /* [3] is the reason for the game end */
3459 /* [4] is a PGN end game-token, preceded by " " */
3460 /* For start-of-game: */
3461 /* [3] begins with "Creating" or "Continuing" */
3462 /* [4] is " *" or empty (don't care). */
3463 int gamenum = atoi(star_match[0]);
3464 char *whitename, *blackname, *why, *endtoken;
3465 ChessMove endtype = (ChessMove) 0;
3468 whitename = star_match[1];
3469 blackname = star_match[2];
3470 why = star_match[3];
3471 endtoken = star_match[4];
3473 whitename = star_match[1];
3474 blackname = star_match[3];
3475 why = star_match[5];
3476 endtoken = star_match[6];
3479 /* Game start messages */
3480 if (strncmp(why, "Creating ", 9) == 0 ||
3481 strncmp(why, "Continuing ", 11) == 0) {
3482 gs_gamenum = gamenum;
3483 strcpy(gs_kind, strchr(why, ' ') + 1);
3484 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3486 if (appData.zippyPlay) {
3487 ZippyGameStart(whitename, blackname);
3490 partnerBoardValid = FALSE; // [HGM] bughouse
3494 /* Game end messages */
3495 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3496 ics_gamenum != gamenum) {
3499 while (endtoken[0] == ' ') endtoken++;
3500 switch (endtoken[0]) {
3503 endtype = GameUnfinished;
3506 endtype = BlackWins;
3509 if (endtoken[1] == '/')
3510 endtype = GameIsDrawn;
3512 endtype = WhiteWins;
3515 GameEnds(endtype, why, GE_ICS);
3517 if (appData.zippyPlay && first.initDone) {
3518 ZippyGameEnd(endtype, why);
3519 if (first.pr == NULL) {
3520 /* Start the next process early so that we'll
3521 be ready for the next challenge */
3522 StartChessProgram(&first);
3524 /* Send "new" early, in case this command takes
3525 a long time to finish, so that we'll be ready
3526 for the next challenge. */
3527 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3531 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3535 if (looking_at(buf, &i, "Removing game * from observation") ||
3536 looking_at(buf, &i, "no longer observing game *") ||
3537 looking_at(buf, &i, "Game * (*) has no examiners")) {
3538 if (gameMode == IcsObserving &&
3539 atoi(star_match[0]) == ics_gamenum)
3541 /* icsEngineAnalyze */
3542 if (appData.icsEngineAnalyze) {
3549 ics_user_moved = FALSE;
3554 if (looking_at(buf, &i, "no longer examining game *")) {
3555 if (gameMode == IcsExamining &&
3556 atoi(star_match[0]) == ics_gamenum)
3560 ics_user_moved = FALSE;
3565 /* Advance leftover_start past any newlines we find,
3566 so only partial lines can get reparsed */
3567 if (looking_at(buf, &i, "\n")) {
3568 prevColor = curColor;
3569 if (curColor != ColorNormal) {
3570 if (oldi > next_out) {
3571 SendToPlayer(&buf[next_out], oldi - next_out);
3574 Colorize(ColorNormal, FALSE);
3575 curColor = ColorNormal;
3577 if (started == STARTED_BOARD) {
3578 started = STARTED_NONE;
3579 parse[parse_pos] = NULLCHAR;
3580 ParseBoard12(parse);
3583 /* Send premove here */
3584 if (appData.premove) {
3586 if (currentMove == 0 &&
3587 gameMode == IcsPlayingWhite &&
3588 appData.premoveWhite) {
3589 sprintf(str, "%s\n", appData.premoveWhiteText);
3590 if (appData.debugMode)
3591 fprintf(debugFP, "Sending premove:\n");
3593 } else if (currentMove == 1 &&
3594 gameMode == IcsPlayingBlack &&
3595 appData.premoveBlack) {
3596 sprintf(str, "%s\n", appData.premoveBlackText);
3597 if (appData.debugMode)
3598 fprintf(debugFP, "Sending premove:\n");
3600 } else if (gotPremove) {
3602 ClearPremoveHighlights();
3603 if (appData.debugMode)
3604 fprintf(debugFP, "Sending premove:\n");
3605 UserMoveEvent(premoveFromX, premoveFromY,
3606 premoveToX, premoveToY,
3611 /* Usually suppress following prompt */
3612 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3613 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3614 if (looking_at(buf, &i, "*% ")) {
3615 savingComment = FALSE;
3620 } else if (started == STARTED_HOLDINGS) {
3622 char new_piece[MSG_SIZ];
3623 started = STARTED_NONE;
3624 parse[parse_pos] = NULLCHAR;
3625 if (appData.debugMode)
3626 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3627 parse, currentMove);
3628 if (sscanf(parse, " game %d", &gamenum) == 1) {
3629 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3630 if (gameInfo.variant == VariantNormal) {
3631 /* [HGM] We seem to switch variant during a game!
3632 * Presumably no holdings were displayed, so we have
3633 * to move the position two files to the right to
3634 * create room for them!
3636 VariantClass newVariant;
3637 switch(gameInfo.boardWidth) { // base guess on board width
3638 case 9: newVariant = VariantShogi; break;
3639 case 10: newVariant = VariantGreat; break;
3640 default: newVariant = VariantCrazyhouse; break;
3642 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3643 /* Get a move list just to see the header, which
3644 will tell us whether this is really bug or zh */
3645 if (ics_getting_history == H_FALSE) {
3646 ics_getting_history = H_REQUESTED;
3647 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651 new_piece[0] = NULLCHAR;
3652 sscanf(parse, "game %d white [%s black [%s <- %s",
3653 &gamenum, white_holding, black_holding,
3655 white_holding[strlen(white_holding)-1] = NULLCHAR;
3656 black_holding[strlen(black_holding)-1] = NULLCHAR;
3657 /* [HGM] copy holdings to board holdings area */
3658 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3659 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3660 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3662 if (appData.zippyPlay && first.initDone) {
3663 ZippyHoldings(white_holding, black_holding,
3667 if (tinyLayout || smallLayout) {
3668 char wh[16], bh[16];
3669 PackHolding(wh, white_holding);
3670 PackHolding(bh, black_holding);
3671 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3672 gameInfo.white, gameInfo.black);
3674 sprintf(str, "%s [%s] vs. %s [%s]",
3675 gameInfo.white, white_holding,
3676 gameInfo.black, black_holding);
3678 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3679 DrawPosition(FALSE, boards[currentMove]);
3681 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3682 sscanf(parse, "game %d white [%s black [%s <- %s",
3683 &gamenum, white_holding, black_holding,
3685 white_holding[strlen(white_holding)-1] = NULLCHAR;
3686 black_holding[strlen(black_holding)-1] = NULLCHAR;
3687 /* [HGM] copy holdings to partner-board holdings area */
3688 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3689 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3690 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3691 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3692 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3695 /* Suppress following prompt */
3696 if (looking_at(buf, &i, "*% ")) {
3697 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3698 savingComment = FALSE;
3706 i++; /* skip unparsed character and loop back */
3709 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3710 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3711 // SendToPlayer(&buf[next_out], i - next_out);
3712 started != STARTED_HOLDINGS && leftover_start > next_out) {
3713 SendToPlayer(&buf[next_out], leftover_start - next_out);
3717 leftover_len = buf_len - leftover_start;
3718 /* if buffer ends with something we couldn't parse,
3719 reparse it after appending the next read */
3721 } else if (count == 0) {
3722 RemoveInputSource(isr);
3723 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3725 DisplayFatalError(_("Error reading from ICS"), error, 1);
3730 /* Board style 12 looks like this:
3732 <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
3734 * The "<12> " is stripped before it gets to this routine. The two
3735 * trailing 0's (flip state and clock ticking) are later addition, and
3736 * some chess servers may not have them, or may have only the first.
3737 * Additional trailing fields may be added in the future.
3740 #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"
3742 #define RELATION_OBSERVING_PLAYED 0
3743 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3744 #define RELATION_PLAYING_MYMOVE 1
3745 #define RELATION_PLAYING_NOTMYMOVE -1
3746 #define RELATION_EXAMINING 2
3747 #define RELATION_ISOLATED_BOARD -3
3748 #define RELATION_STARTING_POSITION -4 /* FICS only */
3751 ParseBoard12(string)
3754 GameMode newGameMode;
3755 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3756 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3757 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3758 char to_play, board_chars[200];
3759 char move_str[500], str[500], elapsed_time[500];
3760 char black[32], white[32];
3762 int prevMove = currentMove;
3765 int fromX, fromY, toX, toY;
3767 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3768 char *bookHit = NULL; // [HGM] book
3769 Boolean weird = FALSE, reqFlag = FALSE, repaint = FALSE;
3771 fromX = fromY = toX = toY = -1;
3775 if (appData.debugMode)
3776 fprintf(debugFP, _("Parsing board: %s\n"), string);
3778 move_str[0] = NULLCHAR;
3779 elapsed_time[0] = NULLCHAR;
3780 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3782 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3783 if(string[i] == ' ') { ranks++; files = 0; }
3785 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3788 for(j = 0; j <i; j++) board_chars[j] = string[j];
3789 board_chars[i] = '\0';
3792 n = sscanf(string, PATTERN, &to_play, &double_push,
3793 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3794 &gamenum, white, black, &relation, &basetime, &increment,
3795 &white_stren, &black_stren, &white_time, &black_time,
3796 &moveNum, str, elapsed_time, move_str, &ics_flip,
3800 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3801 DisplayError(str, 0);
3805 /* Convert the move number to internal form */
3806 moveNum = (moveNum - 1) * 2;
3807 if (to_play == 'B') moveNum++;
3808 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3809 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3815 case RELATION_OBSERVING_PLAYED:
3816 case RELATION_OBSERVING_STATIC:
3817 if (gamenum == -1) {
3818 /* Old ICC buglet */
3819 relation = RELATION_OBSERVING_STATIC;
3821 newGameMode = IcsObserving;
3823 case RELATION_PLAYING_MYMOVE:
3824 case RELATION_PLAYING_NOTMYMOVE:
3826 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3827 IcsPlayingWhite : IcsPlayingBlack;
3829 case RELATION_EXAMINING:
3830 newGameMode = IcsExamining;
3832 case RELATION_ISOLATED_BOARD:
3834 /* Just display this board. If user was doing something else,
3835 we will forget about it until the next board comes. */
3836 newGameMode = IcsIdle;
3838 case RELATION_STARTING_POSITION:
3839 newGameMode = gameMode;
3843 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3844 && newGameMode == IcsObserving && appData.bgObserve) {
3845 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3846 for (k = 0; k < ranks; k++) {
3847 for (j = 0; j < files; j++)
3848 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3849 if(gameInfo.holdingsWidth > 1) {
3850 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3851 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3854 CopyBoard(partnerBoard, board);
3855 if(appData.dualBoard && !twoBoards) { twoBoards = repaint = 1; InitDrawingSizes(-2,0); }
3856 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3857 if(partnerUp) DrawPosition(repaint, partnerBoard);
3858 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3859 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3860 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3861 DisplayMessage(partnerStatus, "");
3862 partnerBoardValid = TRUE;
3866 /* Modify behavior for initial board display on move listing
3869 switch (ics_getting_history) {
3873 case H_GOT_REQ_HEADER:
3874 case H_GOT_UNREQ_HEADER:
3875 /* This is the initial position of the current game */
3876 gamenum = ics_gamenum;
3877 moveNum = 0; /* old ICS bug workaround */
3878 if (to_play == 'B') {
3879 startedFromSetupPosition = TRUE;
3880 blackPlaysFirst = TRUE;
3882 if (forwardMostMove == 0) forwardMostMove = 1;
3883 if (backwardMostMove == 0) backwardMostMove = 1;
3884 if (currentMove == 0) currentMove = 1;
3886 newGameMode = gameMode;
3887 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3889 case H_GOT_UNWANTED_HEADER:
3890 /* This is an initial board that we don't want */
3892 case H_GETTING_MOVES:
3893 /* Should not happen */
3894 DisplayError(_("Error gathering move list: extra board"), 0);
3895 ics_getting_history = H_FALSE;
3899 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3900 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3901 /* [HGM] We seem to have switched variant unexpectedly
3902 * Try to guess new variant from board size
3904 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3905 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3906 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3907 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3908 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3909 if(!weird) newVariant = VariantNormal;
3910 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3911 /* Get a move list just to see the header, which
3912 will tell us whether this is really bug or zh */
3913 if (ics_getting_history == H_FALSE) {
3914 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3915 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3920 /* Take action if this is the first board of a new game, or of a
3921 different game than is currently being displayed. */
3922 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3923 relation == RELATION_ISOLATED_BOARD) {
3925 /* Forget the old game and get the history (if any) of the new one */
3926 if (gameMode != BeginningOfGame) {
3930 if (appData.autoRaiseBoard) BoardToTop();
3932 if (gamenum == -1) {
3933 newGameMode = IcsIdle;
3934 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3935 appData.getMoveList && !reqFlag) {
3936 /* Need to get game history */
3937 ics_getting_history = H_REQUESTED;
3938 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3942 /* Initially flip the board to have black on the bottom if playing
3943 black or if the ICS flip flag is set, but let the user change
3944 it with the Flip View button. */
3945 flipView = appData.autoFlipView ?
3946 (newGameMode == IcsPlayingBlack) || ics_flip :
3949 /* Done with values from previous mode; copy in new ones */
3950 gameMode = newGameMode;
3952 ics_gamenum = gamenum;
3953 if (gamenum == gs_gamenum) {
3954 int klen = strlen(gs_kind);
3955 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3956 sprintf(str, "ICS %s", gs_kind);
3957 gameInfo.event = StrSave(str);
3959 gameInfo.event = StrSave("ICS game");
3961 gameInfo.site = StrSave(appData.icsHost);
3962 gameInfo.date = PGNDate();
3963 gameInfo.round = StrSave("-");
3964 gameInfo.white = StrSave(white);
3965 gameInfo.black = StrSave(black);
3966 timeControl = basetime * 60 * 1000;
3968 timeIncrement = increment * 1000;
3969 movesPerSession = 0;
3970 gameInfo.timeControl = TimeControlTagValue();
3971 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3972 if (appData.debugMode) {
3973 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3974 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3975 setbuf(debugFP, NULL);
3978 gameInfo.outOfBook = NULL;
3980 /* Do we have the ratings? */
3981 if (strcmp(player1Name, white) == 0 &&
3982 strcmp(player2Name, black) == 0) {
3983 if (appData.debugMode)
3984 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3985 player1Rating, player2Rating);
3986 gameInfo.whiteRating = player1Rating;
3987 gameInfo.blackRating = player2Rating;
3988 } else if (strcmp(player2Name, white) == 0 &&
3989 strcmp(player1Name, black) == 0) {
3990 if (appData.debugMode)
3991 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3992 player2Rating, player1Rating);
3993 gameInfo.whiteRating = player2Rating;
3994 gameInfo.blackRating = player1Rating;
3996 player1Name[0] = player2Name[0] = NULLCHAR;
3998 /* Silence shouts if requested */
3999 if (appData.quietPlay &&
4000 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4001 SendToICS(ics_prefix);
4002 SendToICS("set shout 0\n");
4006 /* Deal with midgame name changes */
4008 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4009 if (gameInfo.white) free(gameInfo.white);
4010 gameInfo.white = StrSave(white);
4012 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4013 if (gameInfo.black) free(gameInfo.black);
4014 gameInfo.black = StrSave(black);
4018 /* Throw away game result if anything actually changes in examine mode */
4019 if (gameMode == IcsExamining && !newGame) {
4020 gameInfo.result = GameUnfinished;
4021 if (gameInfo.resultDetails != NULL) {
4022 free(gameInfo.resultDetails);
4023 gameInfo.resultDetails = NULL;
4027 /* In pausing && IcsExamining mode, we ignore boards coming
4028 in if they are in a different variation than we are. */
4029 if (pauseExamInvalid) return;
4030 if (pausing && gameMode == IcsExamining) {
4031 if (moveNum <= pauseExamForwardMostMove) {
4032 pauseExamInvalid = TRUE;
4033 forwardMostMove = pauseExamForwardMostMove;
4038 if (appData.debugMode) {
4039 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4041 /* Parse the board */
4042 for (k = 0; k < ranks; k++) {
4043 for (j = 0; j < files; j++)
4044 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4045 if(gameInfo.holdingsWidth > 1) {
4046 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4047 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4050 CopyBoard(boards[moveNum], board);
4051 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4053 startedFromSetupPosition =
4054 !CompareBoards(board, initialPosition);
4055 if(startedFromSetupPosition)
4056 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4059 /* [HGM] Set castling rights. Take the outermost Rooks,
4060 to make it also work for FRC opening positions. Note that board12
4061 is really defective for later FRC positions, as it has no way to
4062 indicate which Rook can castle if they are on the same side of King.
4063 For the initial position we grant rights to the outermost Rooks,
4064 and remember thos rights, and we then copy them on positions
4065 later in an FRC game. This means WB might not recognize castlings with
4066 Rooks that have moved back to their original position as illegal,
4067 but in ICS mode that is not its job anyway.
4069 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4070 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4072 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4073 if(board[0][i] == WhiteRook) j = i;
4074 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4075 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4076 if(board[0][i] == WhiteRook) j = i;
4077 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4078 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4079 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4080 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4081 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4082 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4083 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4085 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4086 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4087 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4088 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4089 if(board[BOARD_HEIGHT-1][k] == bKing)
4090 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4091 if(gameInfo.variant == VariantTwoKings) {
4092 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4093 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4094 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4097 r = boards[moveNum][CASTLING][0] = initialRights[0];
4098 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4099 r = boards[moveNum][CASTLING][1] = initialRights[1];
4100 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4101 r = boards[moveNum][CASTLING][3] = initialRights[3];
4102 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4103 r = boards[moveNum][CASTLING][4] = initialRights[4];
4104 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4105 /* wildcastle kludge: always assume King has rights */
4106 r = boards[moveNum][CASTLING][2] = initialRights[2];
4107 r = boards[moveNum][CASTLING][5] = initialRights[5];
4109 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4110 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4113 if (ics_getting_history == H_GOT_REQ_HEADER ||
4114 ics_getting_history == H_GOT_UNREQ_HEADER) {
4115 /* This was an initial position from a move list, not
4116 the current position */
4120 /* Update currentMove and known move number limits */
4121 newMove = newGame || moveNum > forwardMostMove;
4124 forwardMostMove = backwardMostMove = currentMove = moveNum;
4125 if (gameMode == IcsExamining && moveNum == 0) {
4126 /* Workaround for ICS limitation: we are not told the wild
4127 type when starting to examine a game. But if we ask for
4128 the move list, the move list header will tell us */
4129 ics_getting_history = H_REQUESTED;
4130 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4133 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4134 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4136 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4137 /* [HGM] applied this also to an engine that is silently watching */
4138 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4139 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4140 gameInfo.variant == currentlyInitializedVariant) {
4141 takeback = forwardMostMove - moveNum;
4142 for (i = 0; i < takeback; i++) {
4143 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4144 SendToProgram("undo\n", &first);
4149 forwardMostMove = moveNum;
4150 if (!pausing || currentMove > forwardMostMove)
4151 currentMove = forwardMostMove;
4153 /* New part of history that is not contiguous with old part */
4154 if (pausing && gameMode == IcsExamining) {
4155 pauseExamInvalid = TRUE;
4156 forwardMostMove = pauseExamForwardMostMove;
4159 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4161 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4162 // [HGM] when we will receive the move list we now request, it will be
4163 // fed to the engine from the first move on. So if the engine is not
4164 // in the initial position now, bring it there.
4165 InitChessProgram(&first, 0);
4168 ics_getting_history = H_REQUESTED;
4169 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4172 forwardMostMove = backwardMostMove = currentMove = moveNum;
4175 /* Update the clocks */
4176 if (strchr(elapsed_time, '.')) {
4178 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4179 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4181 /* Time is in seconds */
4182 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4183 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4188 if (appData.zippyPlay && newGame &&
4189 gameMode != IcsObserving && gameMode != IcsIdle &&
4190 gameMode != IcsExamining)
4191 ZippyFirstBoard(moveNum, basetime, increment);
4194 /* Put the move on the move list, first converting
4195 to canonical algebraic form. */
4197 if (appData.debugMode) {
4198 if (appData.debugMode) { int f = forwardMostMove;
4199 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4200 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4201 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4203 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4204 fprintf(debugFP, "moveNum = %d\n", moveNum);
4205 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4206 setbuf(debugFP, NULL);
4208 if (moveNum <= backwardMostMove) {
4209 /* We don't know what the board looked like before
4211 strcpy(parseList[moveNum - 1], move_str);
4212 strcat(parseList[moveNum - 1], " ");
4213 strcat(parseList[moveNum - 1], elapsed_time);
4214 moveList[moveNum - 1][0] = NULLCHAR;
4215 } else if (strcmp(move_str, "none") == 0) {
4216 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4217 /* Again, we don't know what the board looked like;
4218 this is really the start of the game. */
4219 parseList[moveNum - 1][0] = NULLCHAR;
4220 moveList[moveNum - 1][0] = NULLCHAR;
4221 backwardMostMove = moveNum;
4222 startedFromSetupPosition = TRUE;
4223 fromX = fromY = toX = toY = -1;
4225 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4226 // So we parse the long-algebraic move string in stead of the SAN move
4227 int valid; char buf[MSG_SIZ], *prom;
4229 // str looks something like "Q/a1-a2"; kill the slash
4231 sprintf(buf, "%c%s", str[0], str+2);
4232 else strcpy(buf, str); // might be castling
4233 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4234 strcat(buf, prom); // long move lacks promo specification!
4235 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4236 if(appData.debugMode)
4237 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4238 strcpy(move_str, buf);
4240 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4241 &fromX, &fromY, &toX, &toY, &promoChar)
4242 || ParseOneMove(buf, moveNum - 1, &moveType,
4243 &fromX, &fromY, &toX, &toY, &promoChar);
4244 // end of long SAN patch
4246 (void) CoordsToAlgebraic(boards[moveNum - 1],
4247 PosFlags(moveNum - 1),
4248 fromY, fromX, toY, toX, promoChar,
4249 parseList[moveNum-1]);
4250 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4256 if(gameInfo.variant != VariantShogi)
4257 strcat(parseList[moveNum - 1], "+");
4260 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4261 strcat(parseList[moveNum - 1], "#");
4264 strcat(parseList[moveNum - 1], " ");
4265 strcat(parseList[moveNum - 1], elapsed_time);
4266 /* currentMoveString is set as a side-effect of ParseOneMove */
4267 strcpy(moveList[moveNum - 1], currentMoveString);
4268 strcat(moveList[moveNum - 1], "\n");
4270 /* Move from ICS was illegal!? Punt. */
4271 if (appData.debugMode) {
4272 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4273 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4275 strcpy(parseList[moveNum - 1], move_str);
4276 strcat(parseList[moveNum - 1], " ");
4277 strcat(parseList[moveNum - 1], elapsed_time);
4278 moveList[moveNum - 1][0] = NULLCHAR;
4279 fromX = fromY = toX = toY = -1;
4282 if (appData.debugMode) {
4283 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4284 setbuf(debugFP, NULL);
4288 /* Send move to chess program (BEFORE animating it). */
4289 if (appData.zippyPlay && !newGame && newMove &&
4290 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4292 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4293 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4294 if (moveList[moveNum - 1][0] == NULLCHAR) {
4295 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4297 DisplayError(str, 0);
4299 if (first.sendTime) {
4300 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4302 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4303 if (firstMove && !bookHit) {
4305 if (first.useColors) {
4306 SendToProgram(gameMode == IcsPlayingWhite ?
4308 "black\ngo\n", &first);
4310 SendToProgram("go\n", &first);
4312 first.maybeThinking = TRUE;
4315 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4316 if (moveList[moveNum - 1][0] == NULLCHAR) {
4317 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4318 DisplayError(str, 0);
4320 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4321 SendMoveToProgram(moveNum - 1, &first);
4328 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4329 /* If move comes from a remote source, animate it. If it
4330 isn't remote, it will have already been animated. */
4331 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4332 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4334 if (!pausing && appData.highlightLastMove) {
4335 SetHighlights(fromX, fromY, toX, toY);
4339 /* Start the clocks */
4340 whiteFlag = blackFlag = FALSE;
4341 appData.clockMode = !(basetime == 0 && increment == 0);
4343 ics_clock_paused = TRUE;
4345 } else if (ticking == 1) {
4346 ics_clock_paused = FALSE;
4348 if (gameMode == IcsIdle ||
4349 relation == RELATION_OBSERVING_STATIC ||
4350 relation == RELATION_EXAMINING ||
4352 DisplayBothClocks();
4356 /* Display opponents and material strengths */
4357 if (gameInfo.variant != VariantBughouse &&
4358 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4359 if (tinyLayout || smallLayout) {
4360 if(gameInfo.variant == VariantNormal)
4361 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4362 gameInfo.white, white_stren, gameInfo.black, black_stren,
4363 basetime, increment);
4365 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4366 gameInfo.white, white_stren, gameInfo.black, black_stren,
4367 basetime, increment, (int) gameInfo.variant);
4369 if(gameInfo.variant == VariantNormal)
4370 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4371 gameInfo.white, white_stren, gameInfo.black, black_stren,
4372 basetime, increment);
4374 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4375 gameInfo.white, white_stren, gameInfo.black, black_stren,
4376 basetime, increment, VariantName(gameInfo.variant));
4379 if (appData.debugMode) {
4380 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4385 /* Display the board */
4386 if (!pausing && !appData.noGUI) {
4388 if (appData.premove)
4390 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4391 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4392 ClearPremoveHighlights();
4394 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4395 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4396 DrawPosition(j, boards[currentMove]);
4398 DisplayMove(moveNum - 1);
4399 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4400 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4401 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4402 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4406 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4408 if(bookHit) { // [HGM] book: simulate book reply
4409 static char bookMove[MSG_SIZ]; // a bit generous?
4411 programStats.nodes = programStats.depth = programStats.time =
4412 programStats.score = programStats.got_only_move = 0;
4413 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4415 strcpy(bookMove, "move ");
4416 strcat(bookMove, bookHit);
4417 HandleMachineMove(bookMove, &first);
4426 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4427 ics_getting_history = H_REQUESTED;
4428 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4434 AnalysisPeriodicEvent(force)
4437 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4438 && !force) || !appData.periodicUpdates)
4441 /* Send . command to Crafty to collect stats */
4442 SendToProgram(".\n", &first);
4444 /* Don't send another until we get a response (this makes
4445 us stop sending to old Crafty's which don't understand
4446 the "." command (sending illegal cmds resets node count & time,
4447 which looks bad)) */
4448 programStats.ok_to_send = 0;
4451 void ics_update_width(new_width)
4454 ics_printf("set width %d\n", new_width);
4458 SendMoveToProgram(moveNum, cps)
4460 ChessProgramState *cps;
4464 if (cps->useUsermove) {
4465 SendToProgram("usermove ", cps);
4469 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4470 int len = space - parseList[moveNum];
4471 memcpy(buf, parseList[moveNum], len);
4473 buf[len] = NULLCHAR;
4475 sprintf(buf, "%s\n", parseList[moveNum]);
4477 SendToProgram(buf, cps);
4479 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4480 AlphaRank(moveList[moveNum], 4);
4481 SendToProgram(moveList[moveNum], cps);
4482 AlphaRank(moveList[moveNum], 4); // and back
4484 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4485 * the engine. It would be nice to have a better way to identify castle
4487 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4488 && cps->useOOCastle) {
4489 int fromX = moveList[moveNum][0] - AAA;
4490 int fromY = moveList[moveNum][1] - ONE;
4491 int toX = moveList[moveNum][2] - AAA;
4492 int toY = moveList[moveNum][3] - ONE;
4493 if((boards[moveNum][fromY][fromX] == WhiteKing
4494 && boards[moveNum][toY][toX] == WhiteRook)
4495 || (boards[moveNum][fromY][fromX] == BlackKing
4496 && boards[moveNum][toY][toX] == BlackRook)) {
4497 if(toX > fromX) SendToProgram("O-O\n", cps);
4498 else SendToProgram("O-O-O\n", cps);
4500 else SendToProgram(moveList[moveNum], cps);
4502 else SendToProgram(moveList[moveNum], cps);
4503 /* End of additions by Tord */
4506 /* [HGM] setting up the opening has brought engine in force mode! */
4507 /* Send 'go' if we are in a mode where machine should play. */
4508 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4509 (gameMode == TwoMachinesPlay ||
4511 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4513 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4514 SendToProgram("go\n", cps);
4515 if (appData.debugMode) {
4516 fprintf(debugFP, "(extra)\n");
4519 setboardSpoiledMachineBlack = 0;
4523 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4525 int fromX, fromY, toX, toY;
4527 char user_move[MSG_SIZ];
4531 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4532 (int)moveType, fromX, fromY, toX, toY);
4533 DisplayError(user_move + strlen("say "), 0);
4535 case WhiteKingSideCastle:
4536 case BlackKingSideCastle:
4537 case WhiteQueenSideCastleWild:
4538 case BlackQueenSideCastleWild:
4540 case WhiteHSideCastleFR:
4541 case BlackHSideCastleFR:
4543 sprintf(user_move, "o-o\n");
4545 case WhiteQueenSideCastle:
4546 case BlackQueenSideCastle:
4547 case WhiteKingSideCastleWild:
4548 case BlackKingSideCastleWild:
4550 case WhiteASideCastleFR:
4551 case BlackASideCastleFR:
4553 sprintf(user_move, "o-o-o\n");
4555 case WhitePromotionQueen:
4556 case BlackPromotionQueen:
4557 case WhitePromotionRook:
4558 case BlackPromotionRook:
4559 case WhitePromotionBishop:
4560 case BlackPromotionBishop:
4561 case WhitePromotionKnight:
4562 case BlackPromotionKnight:
4563 case WhitePromotionKing:
4564 case BlackPromotionKing:
4565 case WhitePromotionChancellor:
4566 case BlackPromotionChancellor:
4567 case WhitePromotionArchbishop:
4568 case BlackPromotionArchbishop:
4569 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4570 sprintf(user_move, "%c%c%c%c=%c\n",
4571 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4572 PieceToChar(WhiteFerz));
4573 else if(gameInfo.variant == VariantGreat)
4574 sprintf(user_move, "%c%c%c%c=%c\n",
4575 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4576 PieceToChar(WhiteMan));
4578 sprintf(user_move, "%c%c%c%c=%c\n",
4579 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4580 PieceToChar(PromoPiece(moveType)));
4584 sprintf(user_move, "%c@%c%c\n",
4585 ToUpper(PieceToChar((ChessSquare) fromX)),
4586 AAA + toX, ONE + toY);
4589 case WhiteCapturesEnPassant:
4590 case BlackCapturesEnPassant:
4591 case IllegalMove: /* could be a variant we don't quite understand */
4592 sprintf(user_move, "%c%c%c%c\n",
4593 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4596 SendToICS(user_move);
4597 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4598 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4603 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4604 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4605 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4606 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4607 DisplayError("You cannot do this while you are playing or observing", 0);
4610 if(gameMode != IcsExamining) { // is this ever not the case?
4611 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4613 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4614 sprintf(command, "match %s", ics_handle);
4615 } else { // on FICS we must first go to general examine mode
4616 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4618 if(gameInfo.variant != VariantNormal) {
4619 // try figure out wild number, as xboard names are not always valid on ICS
4620 for(i=1; i<=36; i++) {
4621 sprintf(buf, "wild/%d", i);
4622 if(StringToVariant(buf) == gameInfo.variant) break;
4624 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4625 else if(i == 22) sprintf(buf, "%s fr\n", command);
4626 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4627 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4628 SendToICS(ics_prefix);
4630 if(startedFromSetupPosition || backwardMostMove != 0) {
4631 fen = PositionToFEN(backwardMostMove, NULL);
4632 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4633 sprintf(buf, "loadfen %s\n", fen);
4635 } else { // FICS: everything has to set by separate bsetup commands
4636 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4637 sprintf(buf, "bsetup fen %s\n", fen);
4639 if(!WhiteOnMove(backwardMostMove)) {
4640 SendToICS("bsetup tomove black\n");
4642 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4643 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4645 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4646 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4648 i = boards[backwardMostMove][EP_STATUS];
4649 if(i >= 0) { // set e.p.
4650 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4656 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4657 SendToICS("bsetup done\n"); // switch to normal examining.
4659 for(i = backwardMostMove; i<last; i++) {
4661 sprintf(buf, "%s\n", parseList[i]);
4664 SendToICS(ics_prefix);
4665 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4669 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4674 if (rf == DROP_RANK) {
4675 sprintf(move, "%c@%c%c\n",
4676 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4678 if (promoChar == 'x' || promoChar == NULLCHAR) {
4679 sprintf(move, "%c%c%c%c\n",
4680 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4682 sprintf(move, "%c%c%c%c%c\n",
4683 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4689 ProcessICSInitScript(f)
4694 while (fgets(buf, MSG_SIZ, f)) {
4695 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4702 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4704 AlphaRank(char *move, int n)
4706 // char *p = move, c; int x, y;
4708 if (appData.debugMode) {
4709 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4713 move[2]>='0' && move[2]<='9' &&
4714 move[3]>='a' && move[3]<='x' ) {
4716 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4717 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4719 if(move[0]>='0' && move[0]<='9' &&
4720 move[1]>='a' && move[1]<='x' &&
4721 move[2]>='0' && move[2]<='9' &&
4722 move[3]>='a' && move[3]<='x' ) {
4723 /* input move, Shogi -> normal */
4724 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4725 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4726 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4727 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4730 move[3]>='0' && move[3]<='9' &&
4731 move[2]>='a' && move[2]<='x' ) {
4733 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4734 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4737 move[0]>='a' && move[0]<='x' &&
4738 move[3]>='0' && move[3]<='9' &&
4739 move[2]>='a' && move[2]<='x' ) {
4740 /* output move, normal -> Shogi */
4741 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4742 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4743 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4744 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4745 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4747 if (appData.debugMode) {
4748 fprintf(debugFP, " out = '%s'\n", move);
4752 char yy_textstr[8000];
4754 /* Parser for moves from gnuchess, ICS, or user typein box */
4756 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4759 ChessMove *moveType;
4760 int *fromX, *fromY, *toX, *toY;
4763 if (appData.debugMode) {
4764 fprintf(debugFP, "move to parse: %s\n", move);
4766 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4768 switch (*moveType) {
4769 case WhitePromotionChancellor:
4770 case BlackPromotionChancellor:
4771 case WhitePromotionArchbishop:
4772 case BlackPromotionArchbishop:
4773 case WhitePromotionQueen:
4774 case BlackPromotionQueen:
4775 case WhitePromotionRook:
4776 case BlackPromotionRook:
4777 case WhitePromotionBishop:
4778 case BlackPromotionBishop:
4779 case WhitePromotionKnight:
4780 case BlackPromotionKnight:
4781 case WhitePromotionKing:
4782 case BlackPromotionKing:
4784 case WhiteCapturesEnPassant:
4785 case BlackCapturesEnPassant:
4786 case WhiteKingSideCastle:
4787 case WhiteQueenSideCastle:
4788 case BlackKingSideCastle:
4789 case BlackQueenSideCastle:
4790 case WhiteKingSideCastleWild:
4791 case WhiteQueenSideCastleWild:
4792 case BlackKingSideCastleWild:
4793 case BlackQueenSideCastleWild:
4794 /* Code added by Tord: */
4795 case WhiteHSideCastleFR:
4796 case WhiteASideCastleFR:
4797 case BlackHSideCastleFR:
4798 case BlackASideCastleFR:
4799 /* End of code added by Tord */
4800 case IllegalMove: /* bug or odd chess variant */
4801 *fromX = currentMoveString[0] - AAA;
4802 *fromY = currentMoveString[1] - ONE;
4803 *toX = currentMoveString[2] - AAA;
4804 *toY = currentMoveString[3] - ONE;
4805 *promoChar = currentMoveString[4];
4806 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4807 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4808 if (appData.debugMode) {
4809 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4811 *fromX = *fromY = *toX = *toY = 0;
4814 if (appData.testLegality) {
4815 return (*moveType != IllegalMove);
4817 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4818 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4823 *fromX = *moveType == WhiteDrop ?
4824 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4825 (int) CharToPiece(ToLower(currentMoveString[0]));
4827 *toX = currentMoveString[2] - AAA;
4828 *toY = currentMoveString[3] - ONE;
4829 *promoChar = NULLCHAR;
4833 case ImpossibleMove:
4834 case (ChessMove) 0: /* end of file */
4843 if (appData.debugMode) {
4844 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4847 *fromX = *fromY = *toX = *toY = 0;
4848 *promoChar = NULLCHAR;
4855 ParsePV(char *pv, Boolean storeComments)
4856 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4857 int fromX, fromY, toX, toY; char promoChar;
4862 endPV = forwardMostMove;
4864 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4865 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4866 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4867 if(appData.debugMode){
4868 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);
4870 if(!valid && nr == 0 &&
4871 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4872 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4873 // Hande case where played move is different from leading PV move
4874 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4875 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4876 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4877 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4878 endPV += 2; // if position different, keep this
4879 moveList[endPV-1][0] = fromX + AAA;
4880 moveList[endPV-1][1] = fromY + ONE;
4881 moveList[endPV-1][2] = toX + AAA;
4882 moveList[endPV-1][3] = toY + ONE;
4883 parseList[endPV-1][0] = NULLCHAR;
4884 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4887 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4888 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4889 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4890 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4891 valid++; // allow comments in PV
4895 if(endPV+1 > framePtr) break; // no space, truncate
4898 CopyBoard(boards[endPV], boards[endPV-1]);
4899 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4900 moveList[endPV-1][0] = fromX + AAA;
4901 moveList[endPV-1][1] = fromY + ONE;
4902 moveList[endPV-1][2] = toX + AAA;
4903 moveList[endPV-1][3] = toY + ONE;
4905 CoordsToAlgebraic(boards[endPV - 1],
4906 PosFlags(endPV - 1),
4907 fromY, fromX, toY, toX, promoChar,
4908 parseList[endPV - 1]);
4910 parseList[endPV-1][0] = NULLCHAR;
4912 currentMove = endPV;
4913 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4914 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4915 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4916 DrawPosition(TRUE, boards[currentMove]);
4919 static int lastX, lastY;
4922 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4927 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4928 lastX = x; lastY = y;
4929 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4931 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4932 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4934 do{ while(buf[index] && buf[index] != '\n') index++;
4935 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4937 ParsePV(buf+startPV, FALSE);
4938 *start = startPV; *end = index-1;
4943 LoadPV(int x, int y)
4944 { // called on right mouse click to load PV
4945 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4946 lastX = x; lastY = y;
4947 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4954 if(endPV < 0) return;
4956 currentMove = forwardMostMove;
4957 ClearPremoveHighlights();
4958 DrawPosition(TRUE, boards[currentMove]);
4962 MovePV(int x, int y, int h)
4963 { // step through PV based on mouse coordinates (called on mouse move)
4964 int margin = h>>3, step = 0;
4966 if(endPV < 0) return;
4967 // we must somehow check if right button is still down (might be released off board!)
4968 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4969 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4970 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4972 lastX = x; lastY = y;
4973 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4974 currentMove += step;
4975 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4976 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4977 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4978 DrawPosition(FALSE, boards[currentMove]);
4982 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4983 // All positions will have equal probability, but the current method will not provide a unique
4984 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4990 int piecesLeft[(int)BlackPawn];
4991 int seed, nrOfShuffles;
4993 void GetPositionNumber()
4994 { // sets global variable seed
4997 seed = appData.defaultFrcPosition;
4998 if(seed < 0) { // randomize based on time for negative FRC position numbers
4999 for(i=0; i<50; i++) seed += random();
5000 seed = random() ^ random() >> 8 ^ random() << 8;
5001 if(seed<0) seed = -seed;
5005 int put(Board board, int pieceType, int rank, int n, int shade)
5006 // put the piece on the (n-1)-th empty squares of the given shade
5010 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5011 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5012 board[rank][i] = (ChessSquare) pieceType;
5013 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5015 piecesLeft[pieceType]--;
5023 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5024 // calculate where the next piece goes, (any empty square), and put it there
5028 i = seed % squaresLeft[shade];
5029 nrOfShuffles *= squaresLeft[shade];
5030 seed /= squaresLeft[shade];
5031 put(board, pieceType, rank, i, shade);
5034 void AddTwoPieces(Board board, int pieceType, int rank)
5035 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5037 int i, n=squaresLeft[ANY], j=n-1, k;
5039 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5040 i = seed % k; // pick one
5043 while(i >= j) i -= j--;
5044 j = n - 1 - j; i += j;
5045 put(board, pieceType, rank, j, ANY);
5046 put(board, pieceType, rank, i, ANY);
5049 void SetUpShuffle(Board board, int number)
5053 GetPositionNumber(); nrOfShuffles = 1;
5055 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5056 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5057 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5059 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5061 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5062 p = (int) board[0][i];
5063 if(p < (int) BlackPawn) piecesLeft[p] ++;
5064 board[0][i] = EmptySquare;
5067 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5068 // shuffles restricted to allow normal castling put KRR first
5069 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5070 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5071 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5072 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5073 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5074 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5075 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5076 put(board, WhiteRook, 0, 0, ANY);
5077 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5080 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5081 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5082 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5083 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5084 while(piecesLeft[p] >= 2) {
5085 AddOnePiece(board, p, 0, LITE);
5086 AddOnePiece(board, p, 0, DARK);
5088 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5091 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5092 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5093 // but we leave King and Rooks for last, to possibly obey FRC restriction
5094 if(p == (int)WhiteRook) continue;
5095 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5096 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5099 // now everything is placed, except perhaps King (Unicorn) and Rooks
5101 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5102 // Last King gets castling rights
5103 while(piecesLeft[(int)WhiteUnicorn]) {
5104 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5105 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5108 while(piecesLeft[(int)WhiteKing]) {
5109 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5110 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5115 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5116 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5119 // Only Rooks can be left; simply place them all
5120 while(piecesLeft[(int)WhiteRook]) {
5121 i = put(board, WhiteRook, 0, 0, ANY);
5122 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5125 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5127 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5130 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5131 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5134 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5137 int SetCharTable( char *table, const char * map )
5138 /* [HGM] moved here from winboard.c because of its general usefulness */
5139 /* Basically a safe strcpy that uses the last character as King */
5141 int result = FALSE; int NrPieces;
5143 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5144 && NrPieces >= 12 && !(NrPieces&1)) {
5145 int i; /* [HGM] Accept even length from 12 to 34 */
5147 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5148 for( i=0; i<NrPieces/2-1; i++ ) {
5150 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5152 table[(int) WhiteKing] = map[NrPieces/2-1];
5153 table[(int) BlackKing] = map[NrPieces-1];
5161 void Prelude(Board board)
5162 { // [HGM] superchess: random selection of exo-pieces
5163 int i, j, k; ChessSquare p;
5164 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5166 GetPositionNumber(); // use FRC position number
5168 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5169 SetCharTable(pieceToChar, appData.pieceToCharTable);
5170 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5171 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5174 j = seed%4; seed /= 4;
5175 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5176 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5177 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5178 j = seed%3 + (seed%3 >= j); seed /= 3;
5179 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5180 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5181 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5182 j = seed%3; seed /= 3;
5183 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5184 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5185 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5186 j = seed%2 + (seed%2 >= j); seed /= 2;
5187 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5188 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5189 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5190 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5191 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5192 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5193 put(board, exoPieces[0], 0, 0, ANY);
5194 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5198 InitPosition(redraw)
5201 ChessSquare (* pieces)[BOARD_FILES];
5202 int i, j, pawnRow, overrule,
5203 oldx = gameInfo.boardWidth,
5204 oldy = gameInfo.boardHeight,
5205 oldh = gameInfo.holdingsWidth,
5206 oldv = gameInfo.variant;
5208 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5210 /* [AS] Initialize pv info list [HGM] and game status */
5212 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5213 pvInfoList[i].depth = 0;
5214 boards[i][EP_STATUS] = EP_NONE;
5215 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5218 initialRulePlies = 0; /* 50-move counter start */
5220 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5221 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5225 /* [HGM] logic here is completely changed. In stead of full positions */
5226 /* the initialized data only consist of the two backranks. The switch */
5227 /* selects which one we will use, which is than copied to the Board */
5228 /* initialPosition, which for the rest is initialized by Pawns and */
5229 /* empty squares. This initial position is then copied to boards[0], */
5230 /* possibly after shuffling, so that it remains available. */
5232 gameInfo.holdingsWidth = 0; /* default board sizes */
5233 gameInfo.boardWidth = 8;
5234 gameInfo.boardHeight = 8;
5235 gameInfo.holdingsSize = 0;
5236 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5237 for(i=0; i<BOARD_FILES-2; i++)
5238 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5239 initialPosition[EP_STATUS] = EP_NONE;
5240 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5242 switch (gameInfo.variant) {
5243 case VariantFischeRandom:
5244 shuffleOpenings = TRUE;
5248 case VariantShatranj:
5249 pieces = ShatranjArray;
5250 nrCastlingRights = 0;
5251 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5254 pieces = makrukArray;
5255 nrCastlingRights = 0;
5256 startedFromSetupPosition = TRUE;
5257 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5259 case VariantTwoKings:
5260 pieces = twoKingsArray;
5262 case VariantCapaRandom:
5263 shuffleOpenings = TRUE;
5264 case VariantCapablanca:
5265 pieces = CapablancaArray;
5266 gameInfo.boardWidth = 10;
5267 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5270 pieces = GothicArray;
5271 gameInfo.boardWidth = 10;
5272 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5275 pieces = JanusArray;
5276 gameInfo.boardWidth = 10;
5277 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5278 nrCastlingRights = 6;
5279 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5280 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5281 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5282 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5283 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5284 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5287 pieces = FalconArray;
5288 gameInfo.boardWidth = 10;
5289 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5291 case VariantXiangqi:
5292 pieces = XiangqiArray;
5293 gameInfo.boardWidth = 9;
5294 gameInfo.boardHeight = 10;
5295 nrCastlingRights = 0;
5296 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5299 pieces = ShogiArray;
5300 gameInfo.boardWidth = 9;
5301 gameInfo.boardHeight = 9;
5302 gameInfo.holdingsSize = 7;
5303 nrCastlingRights = 0;
5304 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5306 case VariantCourier:
5307 pieces = CourierArray;
5308 gameInfo.boardWidth = 12;
5309 nrCastlingRights = 0;
5310 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5312 case VariantKnightmate:
5313 pieces = KnightmateArray;
5314 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5317 pieces = fairyArray;
5318 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5321 pieces = GreatArray;
5322 gameInfo.boardWidth = 10;
5323 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5324 gameInfo.holdingsSize = 8;
5328 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5329 gameInfo.holdingsSize = 8;
5330 startedFromSetupPosition = TRUE;
5332 case VariantCrazyhouse:
5333 case VariantBughouse:
5335 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5336 gameInfo.holdingsSize = 5;
5338 case VariantWildCastle:
5340 /* !!?shuffle with kings guaranteed to be on d or e file */
5341 shuffleOpenings = 1;
5343 case VariantNoCastle:
5345 nrCastlingRights = 0;
5346 /* !!?unconstrained back-rank shuffle */
5347 shuffleOpenings = 1;
5352 if(appData.NrFiles >= 0) {
5353 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5354 gameInfo.boardWidth = appData.NrFiles;
5356 if(appData.NrRanks >= 0) {
5357 gameInfo.boardHeight = appData.NrRanks;
5359 if(appData.holdingsSize >= 0) {
5360 i = appData.holdingsSize;
5361 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5362 gameInfo.holdingsSize = i;
5364 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5365 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5366 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5368 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5369 if(pawnRow < 1) pawnRow = 1;
5370 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5372 /* User pieceToChar list overrules defaults */
5373 if(appData.pieceToCharTable != NULL)
5374 SetCharTable(pieceToChar, appData.pieceToCharTable);
5376 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5378 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5379 s = (ChessSquare) 0; /* account holding counts in guard band */
5380 for( i=0; i<BOARD_HEIGHT; i++ )
5381 initialPosition[i][j] = s;
5383 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5384 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5385 initialPosition[pawnRow][j] = WhitePawn;
5386 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5387 if(gameInfo.variant == VariantXiangqi) {
5389 initialPosition[pawnRow][j] =
5390 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5391 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5392 initialPosition[2][j] = WhiteCannon;
5393 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5397 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5399 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5402 initialPosition[1][j] = WhiteBishop;
5403 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5405 initialPosition[1][j] = WhiteRook;
5406 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5409 if( nrCastlingRights == -1) {
5410 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5411 /* This sets default castling rights from none to normal corners */
5412 /* Variants with other castling rights must set them themselves above */
5413 nrCastlingRights = 6;
5415 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5416 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5417 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5418 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5419 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5420 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5423 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5424 if(gameInfo.variant == VariantGreat) { // promotion commoners
5425 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5426 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5427 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5428 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5430 if (appData.debugMode) {
5431 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5433 if(shuffleOpenings) {
5434 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5435 startedFromSetupPosition = TRUE;
5437 if(startedFromPositionFile) {
5438 /* [HGM] loadPos: use PositionFile for every new game */
5439 CopyBoard(initialPosition, filePosition);
5440 for(i=0; i<nrCastlingRights; i++)
5441 initialRights[i] = filePosition[CASTLING][i];
5442 startedFromSetupPosition = TRUE;
5445 CopyBoard(boards[0], initialPosition);
5447 if(oldx != gameInfo.boardWidth ||
5448 oldy != gameInfo.boardHeight ||
5449 oldh != gameInfo.holdingsWidth
5451 || oldv == VariantGothic || // For licensing popups
5452 gameInfo.variant == VariantGothic
5455 || oldv == VariantFalcon ||
5456 gameInfo.variant == VariantFalcon
5459 InitDrawingSizes(-2 ,0);
5462 DrawPosition(TRUE, boards[currentMove]);
5466 SendBoard(cps, moveNum)
5467 ChessProgramState *cps;
5470 char message[MSG_SIZ];
5472 if (cps->useSetboard) {
5473 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5474 sprintf(message, "setboard %s\n", fen);
5475 SendToProgram(message, cps);
5481 /* Kludge to set black to move, avoiding the troublesome and now
5482 * deprecated "black" command.
5484 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5486 SendToProgram("edit\n", cps);
5487 SendToProgram("#\n", cps);
5488 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5489 bp = &boards[moveNum][i][BOARD_LEFT];
5490 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5491 if ((int) *bp < (int) BlackPawn) {
5492 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5494 if(message[0] == '+' || message[0] == '~') {
5495 sprintf(message, "%c%c%c+\n",
5496 PieceToChar((ChessSquare)(DEMOTED *bp)),
5499 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5500 message[1] = BOARD_RGHT - 1 - j + '1';
5501 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5503 SendToProgram(message, cps);
5508 SendToProgram("c\n", cps);
5509 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5510 bp = &boards[moveNum][i][BOARD_LEFT];
5511 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5512 if (((int) *bp != (int) EmptySquare)
5513 && ((int) *bp >= (int) BlackPawn)) {
5514 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5516 if(message[0] == '+' || message[0] == '~') {
5517 sprintf(message, "%c%c%c+\n",
5518 PieceToChar((ChessSquare)(DEMOTED *bp)),
5521 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5522 message[1] = BOARD_RGHT - 1 - j + '1';
5523 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5525 SendToProgram(message, cps);
5530 SendToProgram(".\n", cps);
5532 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5535 static int autoQueen; // [HGM] oneclick
5538 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5540 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5541 /* [HGM] add Shogi promotions */
5542 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5547 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5548 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5550 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5551 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5554 piece = boards[currentMove][fromY][fromX];
5555 if(gameInfo.variant == VariantShogi) {
5556 promotionZoneSize = 3;
5557 highestPromotingPiece = (int)WhiteFerz;
5558 } else if(gameInfo.variant == VariantMakruk) {
5559 promotionZoneSize = 3;
5562 // next weed out all moves that do not touch the promotion zone at all
5563 if((int)piece >= BlackPawn) {
5564 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5566 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5568 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5569 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5572 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5574 // weed out mandatory Shogi promotions
5575 if(gameInfo.variant == VariantShogi) {
5576 if(piece >= BlackPawn) {
5577 if(toY == 0 && piece == BlackPawn ||
5578 toY == 0 && piece == BlackQueen ||
5579 toY <= 1 && piece == BlackKnight) {
5584 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5585 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5586 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5593 // weed out obviously illegal Pawn moves
5594 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5595 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5596 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5597 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5598 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5599 // note we are not allowed to test for valid (non-)capture, due to premove
5602 // we either have a choice what to promote to, or (in Shogi) whether to promote
5603 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5604 *promoChoice = PieceToChar(BlackFerz); // no choice
5607 if(autoQueen) { // predetermined
5608 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5609 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5610 else *promoChoice = PieceToChar(BlackQueen);
5614 // suppress promotion popup on illegal moves that are not premoves
5615 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5616 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5617 if(appData.testLegality && !premove) {
5618 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5619 fromY, fromX, toY, toX, NULLCHAR);
5620 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5621 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5629 InPalace(row, column)
5631 { /* [HGM] for Xiangqi */
5632 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5633 column < (BOARD_WIDTH + 4)/2 &&
5634 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5639 PieceForSquare (x, y)
5643 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5646 return boards[currentMove][y][x];
5650 OKToStartUserMove(x, y)
5653 ChessSquare from_piece;
5656 if (matchMode) return FALSE;
5657 if (gameMode == EditPosition) return TRUE;
5659 if (x >= 0 && y >= 0)
5660 from_piece = boards[currentMove][y][x];
5662 from_piece = EmptySquare;
5664 if (from_piece == EmptySquare) return FALSE;
5666 white_piece = (int)from_piece >= (int)WhitePawn &&
5667 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5670 case PlayFromGameFile:
5672 case TwoMachinesPlay:
5680 case MachinePlaysWhite:
5681 case IcsPlayingBlack:
5682 if (appData.zippyPlay) return FALSE;
5684 DisplayMoveError(_("You are playing Black"));
5689 case MachinePlaysBlack:
5690 case IcsPlayingWhite:
5691 if (appData.zippyPlay) return FALSE;
5693 DisplayMoveError(_("You are playing White"));
5699 if (!white_piece && WhiteOnMove(currentMove)) {
5700 DisplayMoveError(_("It is White's turn"));
5703 if (white_piece && !WhiteOnMove(currentMove)) {
5704 DisplayMoveError(_("It is Black's turn"));
5707 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5708 /* Editing correspondence game history */
5709 /* Could disallow this or prompt for confirmation */
5714 case BeginningOfGame:
5715 if (appData.icsActive) return FALSE;
5716 if (!appData.noChessProgram) {
5718 DisplayMoveError(_("You are playing White"));
5725 if (!white_piece && WhiteOnMove(currentMove)) {
5726 DisplayMoveError(_("It is White's turn"));
5729 if (white_piece && !WhiteOnMove(currentMove)) {
5730 DisplayMoveError(_("It is Black's turn"));
5739 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5740 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5741 && gameMode != AnalyzeFile && gameMode != Training) {
5742 DisplayMoveError(_("Displayed position is not current"));
5749 OnlyMove(int *x, int *y, Boolean captures) {
5750 DisambiguateClosure cl;
5751 if (appData.zippyPlay) return FALSE;
5753 case MachinePlaysBlack:
5754 case IcsPlayingWhite:
5755 case BeginningOfGame:
5756 if(!WhiteOnMove(currentMove)) return FALSE;
5758 case MachinePlaysWhite:
5759 case IcsPlayingBlack:
5760 if(WhiteOnMove(currentMove)) return FALSE;
5765 cl.pieceIn = EmptySquare;
5770 cl.promoCharIn = NULLCHAR;
5771 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5772 if( cl.kind == NormalMove ||
5773 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5774 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5775 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5776 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5783 if(cl.kind != ImpossibleMove) return FALSE;
5784 cl.pieceIn = EmptySquare;
5789 cl.promoCharIn = NULLCHAR;
5790 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5791 if( cl.kind == NormalMove ||
5792 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5793 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5794 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5795 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5800 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5806 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5807 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5808 int lastLoadGameUseList = FALSE;
5809 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5810 ChessMove lastLoadGameStart = (ChessMove) 0;
5813 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5814 int fromX, fromY, toX, toY;
5819 ChessSquare pdown, pup;
5821 /* Check if the user is playing in turn. This is complicated because we
5822 let the user "pick up" a piece before it is his turn. So the piece he
5823 tried to pick up may have been captured by the time he puts it down!
5824 Therefore we use the color the user is supposed to be playing in this
5825 test, not the color of the piece that is currently on the starting
5826 square---except in EditGame mode, where the user is playing both
5827 sides; fortunately there the capture race can't happen. (It can
5828 now happen in IcsExamining mode, but that's just too bad. The user
5829 will get a somewhat confusing message in that case.)
5833 case PlayFromGameFile:
5835 case TwoMachinesPlay:
5839 /* We switched into a game mode where moves are not accepted,
5840 perhaps while the mouse button was down. */
5841 return ImpossibleMove;
5843 case MachinePlaysWhite:
5844 /* User is moving for Black */
5845 if (WhiteOnMove(currentMove)) {
5846 DisplayMoveError(_("It is White's turn"));
5847 return ImpossibleMove;
5851 case MachinePlaysBlack:
5852 /* User is moving for White */
5853 if (!WhiteOnMove(currentMove)) {
5854 DisplayMoveError(_("It is Black's turn"));
5855 return ImpossibleMove;
5861 case BeginningOfGame:
5864 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5865 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5866 /* User is moving for Black */
5867 if (WhiteOnMove(currentMove)) {
5868 DisplayMoveError(_("It is White's turn"));
5869 return ImpossibleMove;
5872 /* User is moving for White */
5873 if (!WhiteOnMove(currentMove)) {
5874 DisplayMoveError(_("It is Black's turn"));
5875 return ImpossibleMove;
5880 case IcsPlayingBlack:
5881 /* User is moving for Black */
5882 if (WhiteOnMove(currentMove)) {
5883 if (!appData.premove) {
5884 DisplayMoveError(_("It is White's turn"));
5885 } else if (toX >= 0 && toY >= 0) {
5888 premoveFromX = fromX;
5889 premoveFromY = fromY;
5890 premovePromoChar = promoChar;
5892 if (appData.debugMode)
5893 fprintf(debugFP, "Got premove: fromX %d,"
5894 "fromY %d, toX %d, toY %d\n",
5895 fromX, fromY, toX, toY);
5897 return ImpossibleMove;
5901 case IcsPlayingWhite:
5902 /* User is moving for White */
5903 if (!WhiteOnMove(currentMove)) {
5904 if (!appData.premove) {
5905 DisplayMoveError(_("It is Black's turn"));
5906 } else if (toX >= 0 && toY >= 0) {
5909 premoveFromX = fromX;
5910 premoveFromY = fromY;
5911 premovePromoChar = promoChar;
5913 if (appData.debugMode)
5914 fprintf(debugFP, "Got premove: fromX %d,"
5915 "fromY %d, toX %d, toY %d\n",
5916 fromX, fromY, toX, toY);
5918 return ImpossibleMove;
5926 /* EditPosition, empty square, or different color piece;
5927 click-click move is possible */
5928 if (toX == -2 || toY == -2) {
5929 boards[0][fromY][fromX] = EmptySquare;
5930 return AmbiguousMove;
5931 } else if (toX >= 0 && toY >= 0) {
5932 boards[0][toY][toX] = boards[0][fromY][fromX];
5933 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5934 if(boards[0][fromY][0] != EmptySquare) {
5935 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5936 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5939 if(fromX == BOARD_RGHT+1) {
5940 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5941 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5942 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5945 boards[0][fromY][fromX] = EmptySquare;
5946 return AmbiguousMove;
5948 return ImpossibleMove;
5951 if(toX < 0 || toY < 0) return ImpossibleMove;
5952 pdown = boards[currentMove][fromY][fromX];
5953 pup = boards[currentMove][toY][toX];
5955 /* [HGM] If move started in holdings, it means a drop */
5956 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5957 if( pup != EmptySquare ) return ImpossibleMove;
5958 if(appData.testLegality) {
5959 /* it would be more logical if LegalityTest() also figured out
5960 * which drops are legal. For now we forbid pawns on back rank.
5961 * Shogi is on its own here...
5963 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5964 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5965 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5967 return WhiteDrop; /* Not needed to specify white or black yet */
5970 /* [HGM] always test for legality, to get promotion info */
5971 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5972 fromY, fromX, toY, toX, promoChar);
5973 /* [HGM] but possibly ignore an IllegalMove result */
5974 if (appData.testLegality) {
5975 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5976 DisplayMoveError(_("Illegal move"));
5977 return ImpossibleMove;
5982 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5983 function is made into one that returns an OK move type if FinishMove
5984 should be called. This to give the calling driver routine the
5985 opportunity to finish the userMove input with a promotion popup,
5986 without bothering the user with this for invalid or illegal moves */
5988 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5991 /* Common tail of UserMoveEvent and DropMenuEvent */
5993 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5995 int fromX, fromY, toX, toY;
5996 /*char*/int promoChar;
6000 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6001 // [HGM] superchess: suppress promotions to non-available piece
6002 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6003 if(WhiteOnMove(currentMove)) {
6004 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6006 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6010 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6011 move type in caller when we know the move is a legal promotion */
6012 if(moveType == NormalMove && promoChar)
6013 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6015 /* [HGM] convert drag-and-drop piece drops to standard form */
6016 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6017 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6018 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6019 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6020 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6021 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6022 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6023 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6027 /* [HGM] <popupFix> The following if has been moved here from
6028 UserMoveEvent(). Because it seemed to belong here (why not allow
6029 piece drops in training games?), and because it can only be
6030 performed after it is known to what we promote. */
6031 if (gameMode == Training) {
6032 /* compare the move played on the board to the next move in the
6033 * game. If they match, display the move and the opponent's response.
6034 * If they don't match, display an error message.
6038 CopyBoard(testBoard, boards[currentMove]);
6039 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6041 if (CompareBoards(testBoard, boards[currentMove+1])) {
6042 ForwardInner(currentMove+1);
6044 /* Autoplay the opponent's response.
6045 * if appData.animate was TRUE when Training mode was entered,
6046 * the response will be animated.
6048 saveAnimate = appData.animate;
6049 appData.animate = animateTraining;
6050 ForwardInner(currentMove+1);
6051 appData.animate = saveAnimate;
6053 /* check for the end of the game */
6054 if (currentMove >= forwardMostMove) {
6055 gameMode = PlayFromGameFile;
6057 SetTrainingModeOff();
6058 DisplayInformation(_("End of game"));
6061 DisplayError(_("Incorrect move"), 0);
6066 /* Ok, now we know that the move is good, so we can kill
6067 the previous line in Analysis Mode */
6068 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6069 && currentMove < forwardMostMove) {
6070 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6073 /* If we need the chess program but it's dead, restart it */
6074 ResurrectChessProgram();
6076 /* A user move restarts a paused game*/
6080 thinkOutput[0] = NULLCHAR;
6082 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6084 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6086 if (gameMode == BeginningOfGame) {
6087 if (appData.noChessProgram) {
6088 gameMode = EditGame;
6092 gameMode = MachinePlaysBlack;
6095 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6097 if (first.sendName) {
6098 sprintf(buf, "name %s\n", gameInfo.white);
6099 SendToProgram(buf, &first);
6106 /* Relay move to ICS or chess engine */
6107 if (appData.icsActive) {
6108 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6109 gameMode == IcsExamining) {
6110 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6111 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6113 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6115 // also send plain move, in case ICS does not understand atomic claims
6116 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6120 if (first.sendTime && (gameMode == BeginningOfGame ||
6121 gameMode == MachinePlaysWhite ||
6122 gameMode == MachinePlaysBlack)) {
6123 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6125 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6126 // [HGM] book: if program might be playing, let it use book
6127 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6128 first.maybeThinking = TRUE;
6129 } else SendMoveToProgram(forwardMostMove-1, &first);
6130 if (currentMove == cmailOldMove + 1) {
6131 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6135 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6139 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6145 if (WhiteOnMove(currentMove)) {
6146 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6148 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6152 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6157 case MachinePlaysBlack:
6158 case MachinePlaysWhite:
6159 /* disable certain menu options while machine is thinking */
6160 SetMachineThinkingEnables();
6167 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6169 if(bookHit) { // [HGM] book: simulate book reply
6170 static char bookMove[MSG_SIZ]; // a bit generous?
6172 programStats.nodes = programStats.depth = programStats.time =
6173 programStats.score = programStats.got_only_move = 0;
6174 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6176 strcpy(bookMove, "move ");
6177 strcat(bookMove, bookHit);
6178 HandleMachineMove(bookMove, &first);
6184 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6185 int fromX, fromY, toX, toY;
6188 /* [HGM] This routine was added to allow calling of its two logical
6189 parts from other modules in the old way. Before, UserMoveEvent()
6190 automatically called FinishMove() if the move was OK, and returned
6191 otherwise. I separated the two, in order to make it possible to
6192 slip a promotion popup in between. But that it always needs two
6193 calls, to the first part, (now called UserMoveTest() ), and to
6194 FinishMove if the first part succeeded. Calls that do not need
6195 to do anything in between, can call this routine the old way.
6197 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6198 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6199 if(moveType == AmbiguousMove)
6200 DrawPosition(FALSE, boards[currentMove]);
6201 else if(moveType != ImpossibleMove && moveType != Comment)
6202 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6206 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6213 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6214 Markers *m = (Markers *) closure;
6215 if(rf == fromY && ff == fromX)
6216 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6217 || kind == WhiteCapturesEnPassant
6218 || kind == BlackCapturesEnPassant);
6219 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6223 MarkTargetSquares(int clear)
6226 if(!appData.markers || !appData.highlightDragging ||
6227 !appData.testLegality || gameMode == EditPosition) return;
6229 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6232 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6233 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6234 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6236 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6239 DrawPosition(TRUE, NULL);
6242 void LeftClick(ClickType clickType, int xPix, int yPix)
6245 Boolean saveAnimate;
6246 static int second = 0, promotionChoice = 0;
6247 char promoChoice = NULLCHAR;
6249 if(appData.seekGraph && appData.icsActive && loggedOn &&
6250 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6251 SeekGraphClick(clickType, xPix, yPix, 0);
6255 if (clickType == Press) ErrorPopDown();
6256 MarkTargetSquares(1);
6258 x = EventToSquare(xPix, BOARD_WIDTH);
6259 y = EventToSquare(yPix, BOARD_HEIGHT);
6260 if (!flipView && y >= 0) {
6261 y = BOARD_HEIGHT - 1 - y;
6263 if (flipView && x >= 0) {
6264 x = BOARD_WIDTH - 1 - x;
6267 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6268 if(clickType == Release) return; // ignore upclick of click-click destination
6269 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6270 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6271 if(gameInfo.holdingsWidth &&
6272 (WhiteOnMove(currentMove)
6273 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6274 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6275 // click in right holdings, for determining promotion piece
6276 ChessSquare p = boards[currentMove][y][x];
6277 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6278 if(p != EmptySquare) {
6279 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6284 DrawPosition(FALSE, boards[currentMove]);
6288 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6289 if(clickType == Press
6290 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6291 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6292 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6295 autoQueen = appData.alwaysPromoteToQueen;
6298 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6299 if (clickType == Press) {
6301 if (OKToStartUserMove(x, y)) {
6305 MarkTargetSquares(0);
6306 DragPieceBegin(xPix, yPix);
6307 if (appData.highlightDragging) {
6308 SetHighlights(x, y, -1, -1);
6317 if (clickType == Press && gameMode != EditPosition) {
6322 // ignore off-board to clicks
6323 if(y < 0 || x < 0) return;
6325 /* Check if clicking again on the same color piece */
6326 fromP = boards[currentMove][fromY][fromX];
6327 toP = boards[currentMove][y][x];
6328 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6329 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6330 WhitePawn <= toP && toP <= WhiteKing &&
6331 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6332 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6333 (BlackPawn <= fromP && fromP <= BlackKing &&
6334 BlackPawn <= toP && toP <= BlackKing &&
6335 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6336 !(fromP == BlackKing && toP == BlackRook && frc))) {
6337 /* Clicked again on same color piece -- changed his mind */
6338 second = (x == fromX && y == fromY);
6339 if(!second || !OnlyMove(&x, &y, TRUE)) {
6340 if (appData.highlightDragging) {
6341 SetHighlights(x, y, -1, -1);
6345 if (OKToStartUserMove(x, y)) {
6348 MarkTargetSquares(0);
6349 DragPieceBegin(xPix, yPix);
6354 // ignore clicks on holdings
6355 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6358 if (clickType == Release && x == fromX && y == fromY) {
6359 DragPieceEnd(xPix, yPix);
6360 if (appData.animateDragging) {
6361 /* Undo animation damage if any */
6362 DrawPosition(FALSE, NULL);
6365 /* Second up/down in same square; just abort move */
6370 ClearPremoveHighlights();
6372 /* First upclick in same square; start click-click mode */
6373 SetHighlights(x, y, -1, -1);
6378 /* we now have a different from- and (possibly off-board) to-square */
6379 /* Completed move */
6382 saveAnimate = appData.animate;
6383 if (clickType == Press) {
6384 /* Finish clickclick move */
6385 if (appData.animate || appData.highlightLastMove) {
6386 SetHighlights(fromX, fromY, toX, toY);
6391 /* Finish drag move */
6392 if (appData.highlightLastMove) {
6393 SetHighlights(fromX, fromY, toX, toY);
6397 DragPieceEnd(xPix, yPix);
6398 /* Don't animate move and drag both */
6399 appData.animate = FALSE;
6402 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6403 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6404 ChessSquare piece = boards[currentMove][fromY][fromX];
6405 if(gameMode == EditPosition && piece != EmptySquare &&
6406 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6409 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6410 n = PieceToNumber(piece - (int)BlackPawn);
6411 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6412 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6413 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6415 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6416 n = PieceToNumber(piece);
6417 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6418 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6419 boards[currentMove][n][BOARD_WIDTH-2]++;
6421 boards[currentMove][fromY][fromX] = EmptySquare;
6425 DrawPosition(TRUE, boards[currentMove]);
6429 // off-board moves should not be highlighted
6430 if(x < 0 || x < 0) ClearHighlights();
6432 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6433 SetHighlights(fromX, fromY, toX, toY);
6434 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6435 // [HGM] super: promotion to captured piece selected from holdings
6436 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6437 promotionChoice = TRUE;
6438 // kludge follows to temporarily execute move on display, without promoting yet
6439 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6440 boards[currentMove][toY][toX] = p;
6441 DrawPosition(FALSE, boards[currentMove]);
6442 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6443 boards[currentMove][toY][toX] = q;
6444 DisplayMessage("Click in holdings to choose piece", "");
6449 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6450 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6451 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6454 appData.animate = saveAnimate;
6455 if (appData.animate || appData.animateDragging) {
6456 /* Undo animation damage if needed */
6457 DrawPosition(FALSE, NULL);
6461 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6462 { // front-end-free part taken out of PieceMenuPopup
6463 int whichMenu; int xSqr, ySqr;
6465 if(seekGraphUp) { // [HGM] seekgraph
6466 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6467 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6471 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6472 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6473 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6474 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6475 if(action == Press) {
6476 originalFlip = flipView;
6477 flipView = !flipView; // temporarily flip board to see game from partners perspective
6478 DrawPosition(TRUE, partnerBoard);
6479 DisplayMessage(partnerStatus, "");
6481 } else if(action == Release) {
6482 flipView = originalFlip;
6483 DrawPosition(TRUE, boards[currentMove]);
6489 xSqr = EventToSquare(x, BOARD_WIDTH);
6490 ySqr = EventToSquare(y, BOARD_HEIGHT);
6491 if (action == Release) UnLoadPV(); // [HGM] pv
6492 if (action != Press) return -2; // return code to be ignored
6495 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6497 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6498 if (xSqr < 0 || ySqr < 0) return -1;
\r
6499 whichMenu = 0; // edit-position menu
6502 if(!appData.icsEngineAnalyze) return -1;
6503 case IcsPlayingWhite:
6504 case IcsPlayingBlack:
6505 if(!appData.zippyPlay) goto noZip;
6508 case MachinePlaysWhite:
6509 case MachinePlaysBlack:
6510 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6511 if (!appData.dropMenu) {
6513 return 2; // flag front-end to grab mouse events
6515 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6516 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6519 if (xSqr < 0 || ySqr < 0) return -1;
6520 if (!appData.dropMenu || appData.testLegality &&
6521 gameInfo.variant != VariantBughouse &&
6522 gameInfo.variant != VariantCrazyhouse) return -1;
6523 whichMenu = 1; // drop menu
6529 if (((*fromX = xSqr) < 0) ||
6530 ((*fromY = ySqr) < 0)) {
6531 *fromX = *fromY = -1;
6535 *fromX = BOARD_WIDTH - 1 - *fromX;
6537 *fromY = BOARD_HEIGHT - 1 - *fromY;
6542 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6544 // char * hint = lastHint;
6545 FrontEndProgramStats stats;
6547 stats.which = cps == &first ? 0 : 1;
6548 stats.depth = cpstats->depth;
6549 stats.nodes = cpstats->nodes;
6550 stats.score = cpstats->score;
6551 stats.time = cpstats->time;
6552 stats.pv = cpstats->movelist;
6553 stats.hint = lastHint;
6554 stats.an_move_index = 0;
6555 stats.an_move_count = 0;
6557 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6558 stats.hint = cpstats->move_name;
6559 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6560 stats.an_move_count = cpstats->nr_moves;
6563 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6565 SetProgramStats( &stats );
6569 Adjudicate(ChessProgramState *cps)
6570 { // [HGM] some adjudications useful with buggy engines
6571 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6572 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6573 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6574 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6575 int k, count = 0; static int bare = 1;
6576 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6577 Boolean canAdjudicate = !appData.icsActive;
6579 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6580 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6581 if( appData.testLegality )
6582 { /* [HGM] Some more adjudications for obstinate engines */
6583 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6584 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6585 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6586 static int moveCount = 6;
6588 char *reason = NULL;
6590 /* Count what is on board. */
6591 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6592 { ChessSquare p = boards[forwardMostMove][i][j];
6596 { /* count B,N,R and other of each side */
6599 NrK++; break; // [HGM] atomic: count Kings
6603 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6604 bishopsColor |= 1 << ((i^j)&1);
6609 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6610 bishopsColor |= 1 << ((i^j)&1);
6625 PawnAdvance += m; NrPawns++;
6627 NrPieces += (p != EmptySquare);
6628 NrW += ((int)p < (int)BlackPawn);
6629 if(gameInfo.variant == VariantXiangqi &&
6630 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6631 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6632 NrW -= ((int)p < (int)BlackPawn);
6636 /* Some material-based adjudications that have to be made before stalemate test */
6637 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6638 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6639 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6640 if(canAdjudicate && appData.checkMates) {
6642 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6643 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6644 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6645 "Xboard adjudication: King destroyed", GE_XBOARD );
6650 /* Bare King in Shatranj (loses) or Losers (wins) */
6651 if( NrW == 1 || NrPieces - NrW == 1) {
6652 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6653 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6654 if(canAdjudicate && appData.checkMates) {
6656 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6657 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6658 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6659 "Xboard adjudication: Bare king", GE_XBOARD );
6663 if( gameInfo.variant == VariantShatranj && --bare < 0)
6665 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6666 if(canAdjudicate && appData.checkMates) {
6667 /* but only adjudicate if adjudication enabled */
6669 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6670 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6671 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6672 "Xboard adjudication: Bare king", GE_XBOARD );
6679 // don't wait for engine to announce game end if we can judge ourselves
6680 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6682 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6683 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6684 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6685 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6688 reason = "Xboard adjudication: 3rd check";
6689 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6699 reason = "Xboard adjudication: Stalemate";
6700 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6701 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6702 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6703 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6704 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6705 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6706 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6707 EP_CHECKMATE : EP_WINS);
6708 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6709 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6713 reason = "Xboard adjudication: Checkmate";
6714 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6718 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6720 result = GameIsDrawn; break;
6722 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6724 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6726 result = (ChessMove) 0;
6728 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6730 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6731 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6732 GameEnds( result, reason, GE_XBOARD );
6736 /* Next absolutely insufficient mating material. */
6737 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6738 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6739 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6740 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6741 { /* KBK, KNK, KK of KBKB with like Bishops */
6743 /* always flag draws, for judging claims */
6744 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6746 if(canAdjudicate && appData.materialDraws) {
6747 /* but only adjudicate them if adjudication enabled */
6748 if(engineOpponent) {
6749 SendToProgram("force\n", engineOpponent); // suppress reply
6750 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6752 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6753 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6758 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6760 ( NrWR == 1 && NrBR == 1 /* KRKR */
6761 || NrWQ==1 && NrBQ==1 /* KQKQ */
6762 || NrWN==2 || NrBN==2 /* KNNK */
6763 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6765 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6766 { /* if the first 3 moves do not show a tactical win, declare draw */
6767 if(engineOpponent) {
6768 SendToProgram("force\n", engineOpponent); // suppress reply
6769 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6771 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6772 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6775 } else moveCount = 6;
6779 if (appData.debugMode) { int i;
6780 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6781 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6782 appData.drawRepeats);
6783 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6784 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6788 // Repetition draws and 50-move rule can be applied independently of legality testing
6790 /* Check for rep-draws */
6792 for(k = forwardMostMove-2;
6793 k>=backwardMostMove && k>=forwardMostMove-100 &&
6794 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6795 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6798 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6799 /* compare castling rights */
6800 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6801 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6802 rights++; /* King lost rights, while rook still had them */
6803 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6804 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6805 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6806 rights++; /* but at least one rook lost them */
6808 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6809 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6811 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6812 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6813 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6816 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6817 && appData.drawRepeats > 1) {
6818 /* adjudicate after user-specified nr of repeats */
6819 if(engineOpponent) {
6820 SendToProgram("force\n", engineOpponent); // suppress reply
6821 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6823 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6824 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6825 // [HGM] xiangqi: check for forbidden perpetuals
6826 int m, ourPerpetual = 1, hisPerpetual = 1;
6827 for(m=forwardMostMove; m>k; m-=2) {
6828 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6829 ourPerpetual = 0; // the current mover did not always check
6830 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6831 hisPerpetual = 0; // the opponent did not always check
6833 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6834 ourPerpetual, hisPerpetual);
6835 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6836 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6837 "Xboard adjudication: perpetual checking", GE_XBOARD );
6840 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6841 break; // (or we would have caught him before). Abort repetition-checking loop.
6842 // Now check for perpetual chases
6843 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6844 hisPerpetual = PerpetualChase(k, forwardMostMove);
6845 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6846 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6847 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6848 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6851 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6852 break; // Abort repetition-checking loop.
6854 // if neither of us is checking or chasing all the time, or both are, it is draw
6856 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6859 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6860 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6864 /* Now we test for 50-move draws. Determine ply count */
6865 count = forwardMostMove;
6866 /* look for last irreversble move */
6867 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6869 /* if we hit starting position, add initial plies */
6870 if( count == backwardMostMove )
6871 count -= initialRulePlies;
6872 count = forwardMostMove - count;
6874 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6875 /* this is used to judge if draw claims are legal */
6876 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6877 if(engineOpponent) {
6878 SendToProgram("force\n", engineOpponent); // suppress reply
6879 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6881 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6886 /* if draw offer is pending, treat it as a draw claim
6887 * when draw condition present, to allow engines a way to
6888 * claim draws before making their move to avoid a race
6889 * condition occurring after their move
6891 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6893 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6894 p = "Draw claim: 50-move rule";
6895 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6896 p = "Draw claim: 3-fold repetition";
6897 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6898 p = "Draw claim: insufficient mating material";
6899 if( p != NULL && canAdjudicate) {
6900 if(engineOpponent) {
6901 SendToProgram("force\n", engineOpponent); // suppress reply
6902 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6904 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6905 GameEnds( GameIsDrawn, p, GE_XBOARD );
6910 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6911 if(engineOpponent) {
6912 SendToProgram("force\n", engineOpponent); // suppress reply
6913 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6915 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6916 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6922 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6923 { // [HGM] book: this routine intercepts moves to simulate book replies
6924 char *bookHit = NULL;
6926 //first determine if the incoming move brings opponent into his book
6927 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6928 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6929 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6930 if(bookHit != NULL && !cps->bookSuspend) {
6931 // make sure opponent is not going to reply after receiving move to book position
6932 SendToProgram("force\n", cps);
6933 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6935 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6936 // now arrange restart after book miss
6938 // after a book hit we never send 'go', and the code after the call to this routine
6939 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6941 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6942 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6943 SendToProgram(buf, cps);
6944 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6945 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6946 SendToProgram("go\n", cps);
6947 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6948 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6949 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6950 SendToProgram("go\n", cps);
6951 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6953 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6957 ChessProgramState *savedState;
6958 void DeferredBookMove(void)
6960 if(savedState->lastPing != savedState->lastPong)
6961 ScheduleDelayedEvent(DeferredBookMove, 10);
6963 HandleMachineMove(savedMessage, savedState);
6967 HandleMachineMove(message, cps)
6969 ChessProgramState *cps;
6971 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6972 char realname[MSG_SIZ];
6973 int fromX, fromY, toX, toY;
6982 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6984 * Kludge to ignore BEL characters
6986 while (*message == '\007') message++;
6989 * [HGM] engine debug message: ignore lines starting with '#' character
6991 if(cps->debug && *message == '#') return;
6994 * Look for book output
6996 if (cps == &first && bookRequested) {
6997 if (message[0] == '\t' || message[0] == ' ') {
6998 /* Part of the book output is here; append it */
6999 strcat(bookOutput, message);
7000 strcat(bookOutput, " \n");
7002 } else if (bookOutput[0] != NULLCHAR) {
7003 /* All of book output has arrived; display it */
7004 char *p = bookOutput;
7005 while (*p != NULLCHAR) {
7006 if (*p == '\t') *p = ' ';
7009 DisplayInformation(bookOutput);
7010 bookRequested = FALSE;
7011 /* Fall through to parse the current output */
7016 * Look for machine move.
7018 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7019 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7021 /* This method is only useful on engines that support ping */
7022 if (cps->lastPing != cps->lastPong) {
7023 if (gameMode == BeginningOfGame) {
7024 /* Extra move from before last new; ignore */
7025 if (appData.debugMode) {
7026 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7029 if (appData.debugMode) {
7030 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7031 cps->which, gameMode);
7034 SendToProgram("undo\n", cps);
7040 case BeginningOfGame:
7041 /* Extra move from before last reset; ignore */
7042 if (appData.debugMode) {
7043 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7050 /* Extra move after we tried to stop. The mode test is
7051 not a reliable way of detecting this problem, but it's
7052 the best we can do on engines that don't support ping.
7054 if (appData.debugMode) {
7055 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7056 cps->which, gameMode);
7058 SendToProgram("undo\n", cps);
7061 case MachinePlaysWhite:
7062 case IcsPlayingWhite:
7063 machineWhite = TRUE;
7066 case MachinePlaysBlack:
7067 case IcsPlayingBlack:
7068 machineWhite = FALSE;
7071 case TwoMachinesPlay:
7072 machineWhite = (cps->twoMachinesColor[0] == 'w');
7075 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7076 if (appData.debugMode) {
7078 "Ignoring move out of turn by %s, gameMode %d"
7079 ", forwardMost %d\n",
7080 cps->which, gameMode, forwardMostMove);
7085 if (appData.debugMode) { int f = forwardMostMove;
7086 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7087 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7088 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7090 if(cps->alphaRank) AlphaRank(machineMove, 4);
7091 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7092 &fromX, &fromY, &toX, &toY, &promoChar)) {
7093 /* Machine move could not be parsed; ignore it. */
7094 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7095 machineMove, cps->which);
7096 DisplayError(buf1, 0);
7097 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7098 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7099 if (gameMode == TwoMachinesPlay) {
7100 GameEnds(machineWhite ? BlackWins : WhiteWins,
7106 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7107 /* So we have to redo legality test with true e.p. status here, */
7108 /* to make sure an illegal e.p. capture does not slip through, */
7109 /* to cause a forfeit on a justified illegal-move complaint */
7110 /* of the opponent. */
7111 if( gameMode==TwoMachinesPlay && appData.testLegality
7112 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7115 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7116 fromY, fromX, toY, toX, promoChar);
7117 if (appData.debugMode) {
7119 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7120 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7121 fprintf(debugFP, "castling rights\n");
7123 if(moveType == IllegalMove) {
7124 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7125 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7126 GameEnds(machineWhite ? BlackWins : WhiteWins,
7129 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7130 /* [HGM] Kludge to handle engines that send FRC-style castling
7131 when they shouldn't (like TSCP-Gothic) */
7133 case WhiteASideCastleFR:
7134 case BlackASideCastleFR:
7136 currentMoveString[2]++;
7138 case WhiteHSideCastleFR:
7139 case BlackHSideCastleFR:
7141 currentMoveString[2]--;
7143 default: ; // nothing to do, but suppresses warning of pedantic compilers
7146 hintRequested = FALSE;
7147 lastHint[0] = NULLCHAR;
7148 bookRequested = FALSE;
7149 /* Program may be pondering now */
7150 cps->maybeThinking = TRUE;
7151 if (cps->sendTime == 2) cps->sendTime = 1;
7152 if (cps->offeredDraw) cps->offeredDraw--;
7154 /* currentMoveString is set as a side-effect of ParseOneMove */
7155 strcpy(machineMove, currentMoveString);
7156 strcat(machineMove, "\n");
7157 strcpy(moveList[forwardMostMove], machineMove);
7159 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7161 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7162 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7165 while( count < adjudicateLossPlies ) {
7166 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7169 score = -score; /* Flip score for winning side */
7172 if( score > adjudicateLossThreshold ) {
7179 if( count >= adjudicateLossPlies ) {
7180 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7182 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7183 "Xboard adjudication",
7190 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7193 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7195 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7196 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7198 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7200 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7202 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7203 char buf[3*MSG_SIZ];
7205 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7206 programStats.score / 100.,
7208 programStats.time / 100.,
7209 (unsigned int)programStats.nodes,
7210 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7211 programStats.movelist);
7213 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7218 /* [AS] Save move info and clear stats for next move */
7219 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7220 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7221 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7222 ClearProgramStats();
7223 thinkOutput[0] = NULLCHAR;
7224 hiddenThinkOutputState = 0;
7227 if (gameMode == TwoMachinesPlay) {
7228 /* [HGM] relaying draw offers moved to after reception of move */
7229 /* and interpreting offer as claim if it brings draw condition */
7230 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7231 SendToProgram("draw\n", cps->other);
7233 if (cps->other->sendTime) {
7234 SendTimeRemaining(cps->other,
7235 cps->other->twoMachinesColor[0] == 'w');
7237 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7238 if (firstMove && !bookHit) {
7240 if (cps->other->useColors) {
7241 SendToProgram(cps->other->twoMachinesColor, cps->other);
7243 SendToProgram("go\n", cps->other);
7245 cps->other->maybeThinking = TRUE;
7248 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7250 if (!pausing && appData.ringBellAfterMoves) {
7255 * Reenable menu items that were disabled while
7256 * machine was thinking
7258 if (gameMode != TwoMachinesPlay)
7259 SetUserThinkingEnables();
7261 // [HGM] book: after book hit opponent has received move and is now in force mode
7262 // force the book reply into it, and then fake that it outputted this move by jumping
7263 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7265 static char bookMove[MSG_SIZ]; // a bit generous?
7267 strcpy(bookMove, "move ");
7268 strcat(bookMove, bookHit);
7271 programStats.nodes = programStats.depth = programStats.time =
7272 programStats.score = programStats.got_only_move = 0;
7273 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7275 if(cps->lastPing != cps->lastPong) {
7276 savedMessage = message; // args for deferred call
7278 ScheduleDelayedEvent(DeferredBookMove, 10);
7287 /* Set special modes for chess engines. Later something general
7288 * could be added here; for now there is just one kludge feature,
7289 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7290 * when "xboard" is given as an interactive command.
7292 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7293 cps->useSigint = FALSE;
7294 cps->useSigterm = FALSE;
7296 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7297 ParseFeatures(message+8, cps);
7298 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7301 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7302 * want this, I was asked to put it in, and obliged.
7304 if (!strncmp(message, "setboard ", 9)) {
7305 Board initial_position;
7307 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7309 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7310 DisplayError(_("Bad FEN received from engine"), 0);
7314 CopyBoard(boards[0], initial_position);
7315 initialRulePlies = FENrulePlies;
7316 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7317 else gameMode = MachinePlaysBlack;
7318 DrawPosition(FALSE, boards[currentMove]);
7324 * Look for communication commands
7326 if (!strncmp(message, "telluser ", 9)) {
7327 DisplayNote(message + 9);
7330 if (!strncmp(message, "tellusererror ", 14)) {
7332 DisplayError(message + 14, 0);
7335 if (!strncmp(message, "tellopponent ", 13)) {
7336 if (appData.icsActive) {
7338 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7342 DisplayNote(message + 13);
7346 if (!strncmp(message, "tellothers ", 11)) {
7347 if (appData.icsActive) {
7349 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7355 if (!strncmp(message, "tellall ", 8)) {
7356 if (appData.icsActive) {
7358 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7362 DisplayNote(message + 8);
7366 if (strncmp(message, "warning", 7) == 0) {
7367 /* Undocumented feature, use tellusererror in new code */
7368 DisplayError(message, 0);
7371 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7372 strcpy(realname, cps->tidy);
7373 strcat(realname, " query");
7374 AskQuestion(realname, buf2, buf1, cps->pr);
7377 /* Commands from the engine directly to ICS. We don't allow these to be
7378 * sent until we are logged on. Crafty kibitzes have been known to
7379 * interfere with the login process.
7382 if (!strncmp(message, "tellics ", 8)) {
7383 SendToICS(message + 8);
7387 if (!strncmp(message, "tellicsnoalias ", 15)) {
7388 SendToICS(ics_prefix);
7389 SendToICS(message + 15);
7393 /* The following are for backward compatibility only */
7394 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7395 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7396 SendToICS(ics_prefix);
7402 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7406 * If the move is illegal, cancel it and redraw the board.
7407 * Also deal with other error cases. Matching is rather loose
7408 * here to accommodate engines written before the spec.
7410 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7411 strncmp(message, "Error", 5) == 0) {
7412 if (StrStr(message, "name") ||
7413 StrStr(message, "rating") || StrStr(message, "?") ||
7414 StrStr(message, "result") || StrStr(message, "board") ||
7415 StrStr(message, "bk") || StrStr(message, "computer") ||
7416 StrStr(message, "variant") || StrStr(message, "hint") ||
7417 StrStr(message, "random") || StrStr(message, "depth") ||
7418 StrStr(message, "accepted")) {
7421 if (StrStr(message, "protover")) {
7422 /* Program is responding to input, so it's apparently done
7423 initializing, and this error message indicates it is
7424 protocol version 1. So we don't need to wait any longer
7425 for it to initialize and send feature commands. */
7426 FeatureDone(cps, 1);
7427 cps->protocolVersion = 1;
7430 cps->maybeThinking = FALSE;
7432 if (StrStr(message, "draw")) {
7433 /* Program doesn't have "draw" command */
7434 cps->sendDrawOffers = 0;
7437 if (cps->sendTime != 1 &&
7438 (StrStr(message, "time") || StrStr(message, "otim"))) {
7439 /* Program apparently doesn't have "time" or "otim" command */
7443 if (StrStr(message, "analyze")) {
7444 cps->analysisSupport = FALSE;
7445 cps->analyzing = FALSE;
7447 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7448 DisplayError(buf2, 0);
7451 if (StrStr(message, "(no matching move)st")) {
7452 /* Special kludge for GNU Chess 4 only */
7453 cps->stKludge = TRUE;
7454 SendTimeControl(cps, movesPerSession, timeControl,
7455 timeIncrement, appData.searchDepth,
7459 if (StrStr(message, "(no matching move)sd")) {
7460 /* Special kludge for GNU Chess 4 only */
7461 cps->sdKludge = TRUE;
7462 SendTimeControl(cps, movesPerSession, timeControl,
7463 timeIncrement, appData.searchDepth,
7467 if (!StrStr(message, "llegal")) {
7470 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7471 gameMode == IcsIdle) return;
7472 if (forwardMostMove <= backwardMostMove) return;
7473 if (pausing) PauseEvent();
7474 if(appData.forceIllegal) {
7475 // [HGM] illegal: machine refused move; force position after move into it
7476 SendToProgram("force\n", cps);
7477 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7478 // we have a real problem now, as SendBoard will use the a2a3 kludge
7479 // when black is to move, while there might be nothing on a2 or black
7480 // might already have the move. So send the board as if white has the move.
7481 // But first we must change the stm of the engine, as it refused the last move
7482 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7483 if(WhiteOnMove(forwardMostMove)) {
7484 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7485 SendBoard(cps, forwardMostMove); // kludgeless board
7487 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7488 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7489 SendBoard(cps, forwardMostMove+1); // kludgeless board
7491 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7492 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7493 gameMode == TwoMachinesPlay)
7494 SendToProgram("go\n", cps);
7497 if (gameMode == PlayFromGameFile) {
7498 /* Stop reading this game file */
7499 gameMode = EditGame;
7502 currentMove = forwardMostMove-1;
7503 DisplayMove(currentMove-1); /* before DisplayMoveError */
7504 SwitchClocks(forwardMostMove-1); // [HGM] race
7505 DisplayBothClocks();
7506 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7507 parseList[currentMove], cps->which);
7508 DisplayMoveError(buf1);
7509 DrawPosition(FALSE, boards[currentMove]);
7511 /* [HGM] illegal-move claim should forfeit game when Xboard */
7512 /* only passes fully legal moves */
7513 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7514 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7515 "False illegal-move claim", GE_XBOARD );
7519 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7520 /* Program has a broken "time" command that
7521 outputs a string not ending in newline.
7527 * If chess program startup fails, exit with an error message.
7528 * Attempts to recover here are futile.
7530 if ((StrStr(message, "unknown host") != NULL)
7531 || (StrStr(message, "No remote directory") != NULL)
7532 || (StrStr(message, "not found") != NULL)
7533 || (StrStr(message, "No such file") != NULL)
7534 || (StrStr(message, "can't alloc") != NULL)
7535 || (StrStr(message, "Permission denied") != NULL)) {
7537 cps->maybeThinking = FALSE;
7538 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7539 cps->which, cps->program, cps->host, message);
7540 RemoveInputSource(cps->isr);
7541 DisplayFatalError(buf1, 0, 1);
7546 * Look for hint output
7548 if (sscanf(message, "Hint: %s", buf1) == 1) {
7549 if (cps == &first && hintRequested) {
7550 hintRequested = FALSE;
7551 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7552 &fromX, &fromY, &toX, &toY, &promoChar)) {
7553 (void) CoordsToAlgebraic(boards[forwardMostMove],
7554 PosFlags(forwardMostMove),
7555 fromY, fromX, toY, toX, promoChar, buf1);
7556 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7557 DisplayInformation(buf2);
7559 /* Hint move could not be parsed!? */
7560 snprintf(buf2, sizeof(buf2),
7561 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7563 DisplayError(buf2, 0);
7566 strcpy(lastHint, buf1);
7572 * Ignore other messages if game is not in progress
7574 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7575 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7578 * look for win, lose, draw, or draw offer
7580 if (strncmp(message, "1-0", 3) == 0) {
7581 char *p, *q, *r = "";
7582 p = strchr(message, '{');
7590 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7592 } else if (strncmp(message, "0-1", 3) == 0) {
7593 char *p, *q, *r = "";
7594 p = strchr(message, '{');
7602 /* Kludge for Arasan 4.1 bug */
7603 if (strcmp(r, "Black resigns") == 0) {
7604 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7607 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7609 } else if (strncmp(message, "1/2", 3) == 0) {
7610 char *p, *q, *r = "";
7611 p = strchr(message, '{');
7620 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7623 } else if (strncmp(message, "White resign", 12) == 0) {
7624 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7626 } else if (strncmp(message, "Black resign", 12) == 0) {
7627 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7629 } else if (strncmp(message, "White matches", 13) == 0 ||
7630 strncmp(message, "Black matches", 13) == 0 ) {
7631 /* [HGM] ignore GNUShogi noises */
7633 } else if (strncmp(message, "White", 5) == 0 &&
7634 message[5] != '(' &&
7635 StrStr(message, "Black") == NULL) {
7636 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7638 } else if (strncmp(message, "Black", 5) == 0 &&
7639 message[5] != '(') {
7640 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7642 } else if (strcmp(message, "resign") == 0 ||
7643 strcmp(message, "computer resigns") == 0) {
7645 case MachinePlaysBlack:
7646 case IcsPlayingBlack:
7647 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7649 case MachinePlaysWhite:
7650 case IcsPlayingWhite:
7651 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7653 case TwoMachinesPlay:
7654 if (cps->twoMachinesColor[0] == 'w')
7655 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7657 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7664 } else if (strncmp(message, "opponent mates", 14) == 0) {
7666 case MachinePlaysBlack:
7667 case IcsPlayingBlack:
7668 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7670 case MachinePlaysWhite:
7671 case IcsPlayingWhite:
7672 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7674 case TwoMachinesPlay:
7675 if (cps->twoMachinesColor[0] == 'w')
7676 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7678 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7685 } else if (strncmp(message, "computer mates", 14) == 0) {
7687 case MachinePlaysBlack:
7688 case IcsPlayingBlack:
7689 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7691 case MachinePlaysWhite:
7692 case IcsPlayingWhite:
7693 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7695 case TwoMachinesPlay:
7696 if (cps->twoMachinesColor[0] == 'w')
7697 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7699 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7706 } else if (strncmp(message, "checkmate", 9) == 0) {
7707 if (WhiteOnMove(forwardMostMove)) {
7708 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7710 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7713 } else if (strstr(message, "Draw") != NULL ||
7714 strstr(message, "game is a draw") != NULL) {
7715 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7717 } else if (strstr(message, "offer") != NULL &&
7718 strstr(message, "draw") != NULL) {
7720 if (appData.zippyPlay && first.initDone) {
7721 /* Relay offer to ICS */
7722 SendToICS(ics_prefix);
7723 SendToICS("draw\n");
7726 cps->offeredDraw = 2; /* valid until this engine moves twice */
7727 if (gameMode == TwoMachinesPlay) {
7728 if (cps->other->offeredDraw) {
7729 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7730 /* [HGM] in two-machine mode we delay relaying draw offer */
7731 /* until after we also have move, to see if it is really claim */
7733 } else if (gameMode == MachinePlaysWhite ||
7734 gameMode == MachinePlaysBlack) {
7735 if (userOfferedDraw) {
7736 DisplayInformation(_("Machine accepts your draw offer"));
7737 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7739 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7746 * Look for thinking output
7748 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7749 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7751 int plylev, mvleft, mvtot, curscore, time;
7752 char mvname[MOVE_LEN];
7756 int prefixHint = FALSE;
7757 mvname[0] = NULLCHAR;
7760 case MachinePlaysBlack:
7761 case IcsPlayingBlack:
7762 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7764 case MachinePlaysWhite:
7765 case IcsPlayingWhite:
7766 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7771 case IcsObserving: /* [DM] icsEngineAnalyze */
7772 if (!appData.icsEngineAnalyze) ignore = TRUE;
7774 case TwoMachinesPlay:
7775 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7786 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7787 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7789 if (plyext != ' ' && plyext != '\t') {
7793 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7794 if( cps->scoreIsAbsolute &&
7795 ( gameMode == MachinePlaysBlack ||
7796 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7797 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7798 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7799 !WhiteOnMove(currentMove)
7802 curscore = -curscore;
7806 programStats.depth = plylev;
7807 programStats.nodes = nodes;
7808 programStats.time = time;
7809 programStats.score = curscore;
7810 programStats.got_only_move = 0;
7812 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7815 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7816 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7817 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7818 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7819 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7820 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7821 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7822 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7825 /* Buffer overflow protection */
7826 if (buf1[0] != NULLCHAR) {
7827 if (strlen(buf1) >= sizeof(programStats.movelist)
7828 && appData.debugMode) {
7830 "PV is too long; using the first %u bytes.\n",
7831 (unsigned) sizeof(programStats.movelist) - 1);
7834 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7836 sprintf(programStats.movelist, " no PV\n");
7839 if (programStats.seen_stat) {
7840 programStats.ok_to_send = 1;
7843 if (strchr(programStats.movelist, '(') != NULL) {
7844 programStats.line_is_book = 1;
7845 programStats.nr_moves = 0;
7846 programStats.moves_left = 0;
7848 programStats.line_is_book = 0;
7851 SendProgramStatsToFrontend( cps, &programStats );
7854 [AS] Protect the thinkOutput buffer from overflow... this
7855 is only useful if buf1 hasn't overflowed first!
7857 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7859 (gameMode == TwoMachinesPlay ?
7860 ToUpper(cps->twoMachinesColor[0]) : ' '),
7861 ((double) curscore) / 100.0,
7862 prefixHint ? lastHint : "",
7863 prefixHint ? " " : "" );
7865 if( buf1[0] != NULLCHAR ) {
7866 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7868 if( strlen(buf1) > max_len ) {
7869 if( appData.debugMode) {
7870 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7872 buf1[max_len+1] = '\0';
7875 strcat( thinkOutput, buf1 );
7878 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7879 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7880 DisplayMove(currentMove - 1);
7884 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7885 /* crafty (9.25+) says "(only move) <move>"
7886 * if there is only 1 legal move
7888 sscanf(p, "(only move) %s", buf1);
7889 sprintf(thinkOutput, "%s (only move)", buf1);
7890 sprintf(programStats.movelist, "%s (only move)", buf1);
7891 programStats.depth = 1;
7892 programStats.nr_moves = 1;
7893 programStats.moves_left = 1;
7894 programStats.nodes = 1;
7895 programStats.time = 1;
7896 programStats.got_only_move = 1;
7898 /* Not really, but we also use this member to
7899 mean "line isn't going to change" (Crafty
7900 isn't searching, so stats won't change) */
7901 programStats.line_is_book = 1;
7903 SendProgramStatsToFrontend( cps, &programStats );
7905 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7906 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7907 DisplayMove(currentMove - 1);
7910 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7911 &time, &nodes, &plylev, &mvleft,
7912 &mvtot, mvname) >= 5) {
7913 /* The stat01: line is from Crafty (9.29+) in response
7914 to the "." command */
7915 programStats.seen_stat = 1;
7916 cps->maybeThinking = TRUE;
7918 if (programStats.got_only_move || !appData.periodicUpdates)
7921 programStats.depth = plylev;
7922 programStats.time = time;
7923 programStats.nodes = nodes;
7924 programStats.moves_left = mvleft;
7925 programStats.nr_moves = mvtot;
7926 strcpy(programStats.move_name, mvname);
7927 programStats.ok_to_send = 1;
7928 programStats.movelist[0] = '\0';
7930 SendProgramStatsToFrontend( cps, &programStats );
7934 } else if (strncmp(message,"++",2) == 0) {
7935 /* Crafty 9.29+ outputs this */
7936 programStats.got_fail = 2;
7939 } else if (strncmp(message,"--",2) == 0) {
7940 /* Crafty 9.29+ outputs this */
7941 programStats.got_fail = 1;
7944 } else if (thinkOutput[0] != NULLCHAR &&
7945 strncmp(message, " ", 4) == 0) {
7946 unsigned message_len;
7949 while (*p && *p == ' ') p++;
7951 message_len = strlen( p );
7953 /* [AS] Avoid buffer overflow */
7954 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7955 strcat(thinkOutput, " ");
7956 strcat(thinkOutput, p);
7959 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7960 strcat(programStats.movelist, " ");
7961 strcat(programStats.movelist, p);
7964 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7965 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7966 DisplayMove(currentMove - 1);
7974 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7975 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7977 ChessProgramStats cpstats;
7979 if (plyext != ' ' && plyext != '\t') {
7983 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7984 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7985 curscore = -curscore;
7988 cpstats.depth = plylev;
7989 cpstats.nodes = nodes;
7990 cpstats.time = time;
7991 cpstats.score = curscore;
7992 cpstats.got_only_move = 0;
7993 cpstats.movelist[0] = '\0';
7995 if (buf1[0] != NULLCHAR) {
7996 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7999 cpstats.ok_to_send = 0;
8000 cpstats.line_is_book = 0;
8001 cpstats.nr_moves = 0;
8002 cpstats.moves_left = 0;
8004 SendProgramStatsToFrontend( cps, &cpstats );
8011 /* Parse a game score from the character string "game", and
8012 record it as the history of the current game. The game
8013 score is NOT assumed to start from the standard position.
8014 The display is not updated in any way.
8017 ParseGameHistory(game)
8021 int fromX, fromY, toX, toY, boardIndex;
8026 if (appData.debugMode)
8027 fprintf(debugFP, "Parsing game history: %s\n", game);
8029 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8030 gameInfo.site = StrSave(appData.icsHost);
8031 gameInfo.date = PGNDate();
8032 gameInfo.round = StrSave("-");
8034 /* Parse out names of players */
8035 while (*game == ' ') game++;
8037 while (*game != ' ') *p++ = *game++;
8039 gameInfo.white = StrSave(buf);
8040 while (*game == ' ') game++;
8042 while (*game != ' ' && *game != '\n') *p++ = *game++;
8044 gameInfo.black = StrSave(buf);
8047 boardIndex = blackPlaysFirst ? 1 : 0;
8050 yyboardindex = boardIndex;
8051 moveType = (ChessMove) yylex();
8053 case IllegalMove: /* maybe suicide chess, etc. */
8054 if (appData.debugMode) {
8055 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8056 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8057 setbuf(debugFP, NULL);
8059 case WhitePromotionChancellor:
8060 case BlackPromotionChancellor:
8061 case WhitePromotionArchbishop:
8062 case BlackPromotionArchbishop:
8063 case WhitePromotionQueen:
8064 case BlackPromotionQueen:
8065 case WhitePromotionRook:
8066 case BlackPromotionRook:
8067 case WhitePromotionBishop:
8068 case BlackPromotionBishop:
8069 case WhitePromotionKnight:
8070 case BlackPromotionKnight:
8071 case WhitePromotionKing:
8072 case BlackPromotionKing:
8074 case WhiteCapturesEnPassant:
8075 case BlackCapturesEnPassant:
8076 case WhiteKingSideCastle:
8077 case WhiteQueenSideCastle:
8078 case BlackKingSideCastle:
8079 case BlackQueenSideCastle:
8080 case WhiteKingSideCastleWild:
8081 case WhiteQueenSideCastleWild:
8082 case BlackKingSideCastleWild:
8083 case BlackQueenSideCastleWild:
8085 case WhiteHSideCastleFR:
8086 case WhiteASideCastleFR:
8087 case BlackHSideCastleFR:
8088 case BlackASideCastleFR:
8090 fromX = currentMoveString[0] - AAA;
8091 fromY = currentMoveString[1] - ONE;
8092 toX = currentMoveString[2] - AAA;
8093 toY = currentMoveString[3] - ONE;
8094 promoChar = currentMoveString[4];
8098 fromX = moveType == WhiteDrop ?
8099 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8100 (int) CharToPiece(ToLower(currentMoveString[0]));
8102 toX = currentMoveString[2] - AAA;
8103 toY = currentMoveString[3] - ONE;
8104 promoChar = NULLCHAR;
8108 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8109 if (appData.debugMode) {
8110 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8111 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8112 setbuf(debugFP, NULL);
8114 DisplayError(buf, 0);
8116 case ImpossibleMove:
8118 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8119 if (appData.debugMode) {
8120 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8121 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8122 setbuf(debugFP, NULL);
8124 DisplayError(buf, 0);
8126 case (ChessMove) 0: /* end of file */
8127 if (boardIndex < backwardMostMove) {
8128 /* Oops, gap. How did that happen? */
8129 DisplayError(_("Gap in move list"), 0);
8132 backwardMostMove = blackPlaysFirst ? 1 : 0;
8133 if (boardIndex > forwardMostMove) {
8134 forwardMostMove = boardIndex;
8138 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8139 strcat(parseList[boardIndex-1], " ");
8140 strcat(parseList[boardIndex-1], yy_text);
8152 case GameUnfinished:
8153 if (gameMode == IcsExamining) {
8154 if (boardIndex < backwardMostMove) {
8155 /* Oops, gap. How did that happen? */
8158 backwardMostMove = blackPlaysFirst ? 1 : 0;
8161 gameInfo.result = moveType;
8162 p = strchr(yy_text, '{');
8163 if (p == NULL) p = strchr(yy_text, '(');
8166 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8168 q = strchr(p, *p == '{' ? '}' : ')');
8169 if (q != NULL) *q = NULLCHAR;
8172 gameInfo.resultDetails = StrSave(p);
8175 if (boardIndex >= forwardMostMove &&
8176 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8177 backwardMostMove = blackPlaysFirst ? 1 : 0;
8180 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8181 fromY, fromX, toY, toX, promoChar,
8182 parseList[boardIndex]);
8183 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8184 /* currentMoveString is set as a side-effect of yylex */
8185 strcpy(moveList[boardIndex], currentMoveString);
8186 strcat(moveList[boardIndex], "\n");
8188 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8189 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8195 if(gameInfo.variant != VariantShogi)
8196 strcat(parseList[boardIndex - 1], "+");
8200 strcat(parseList[boardIndex - 1], "#");
8207 /* Apply a move to the given board */
8209 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8210 int fromX, fromY, toX, toY;
8214 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8215 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8217 /* [HGM] compute & store e.p. status and castling rights for new position */
8218 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8221 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8222 oldEP = (signed char)board[EP_STATUS];
8223 board[EP_STATUS] = EP_NONE;
8225 if( board[toY][toX] != EmptySquare )
8226 board[EP_STATUS] = EP_CAPTURE;
8228 if( board[fromY][fromX] == WhitePawn ) {
8229 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8230 board[EP_STATUS] = EP_PAWN_MOVE;
8232 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8233 gameInfo.variant != VariantBerolina || toX < fromX)
8234 board[EP_STATUS] = toX | berolina;
8235 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8236 gameInfo.variant != VariantBerolina || toX > fromX)
8237 board[EP_STATUS] = toX;
8240 if( board[fromY][fromX] == BlackPawn ) {
8241 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8242 board[EP_STATUS] = EP_PAWN_MOVE;
8243 if( toY-fromY== -2) {
8244 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8245 gameInfo.variant != VariantBerolina || toX < fromX)
8246 board[EP_STATUS] = toX | berolina;
8247 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8248 gameInfo.variant != VariantBerolina || toX > fromX)
8249 board[EP_STATUS] = toX;
8253 for(i=0; i<nrCastlingRights; i++) {
8254 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8255 board[CASTLING][i] == toX && castlingRank[i] == toY
8256 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8261 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8262 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8263 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8265 if (fromX == toX && fromY == toY) return;
8267 if (fromY == DROP_RANK) {
8269 piece = board[toY][toX] = (ChessSquare) fromX;
8271 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8272 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8273 if(gameInfo.variant == VariantKnightmate)
8274 king += (int) WhiteUnicorn - (int) WhiteKing;
8276 /* Code added by Tord: */
8277 /* FRC castling assumed when king captures friendly rook. */
8278 if (board[fromY][fromX] == WhiteKing &&
8279 board[toY][toX] == WhiteRook) {
8280 board[fromY][fromX] = EmptySquare;
8281 board[toY][toX] = EmptySquare;
8283 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8285 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8287 } else if (board[fromY][fromX] == BlackKing &&
8288 board[toY][toX] == BlackRook) {
8289 board[fromY][fromX] = EmptySquare;
8290 board[toY][toX] = EmptySquare;
8292 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8294 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8296 /* End of code added by Tord */
8298 } else if (board[fromY][fromX] == king
8299 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8300 && toY == fromY && toX > fromX+1) {
8301 board[fromY][fromX] = EmptySquare;
8302 board[toY][toX] = king;
8303 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8304 board[fromY][BOARD_RGHT-1] = EmptySquare;
8305 } else if (board[fromY][fromX] == king
8306 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8307 && toY == fromY && toX < fromX-1) {
8308 board[fromY][fromX] = EmptySquare;
8309 board[toY][toX] = king;
8310 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8311 board[fromY][BOARD_LEFT] = EmptySquare;
8312 } else if (board[fromY][fromX] == WhitePawn
8313 && toY >= BOARD_HEIGHT-promoRank
8314 && gameInfo.variant != VariantXiangqi
8316 /* white pawn promotion */
8317 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8318 if (board[toY][toX] == EmptySquare) {
8319 board[toY][toX] = WhiteQueen;
8321 if(gameInfo.variant==VariantBughouse ||
8322 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8323 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8324 board[fromY][fromX] = EmptySquare;
8325 } else if ((fromY == BOARD_HEIGHT-4)
8327 && gameInfo.variant != VariantXiangqi
8328 && gameInfo.variant != VariantBerolina
8329 && (board[fromY][fromX] == WhitePawn)
8330 && (board[toY][toX] == EmptySquare)) {
8331 board[fromY][fromX] = EmptySquare;
8332 board[toY][toX] = WhitePawn;
8333 captured = board[toY - 1][toX];
8334 board[toY - 1][toX] = EmptySquare;
8335 } else if ((fromY == BOARD_HEIGHT-4)
8337 && gameInfo.variant == VariantBerolina
8338 && (board[fromY][fromX] == WhitePawn)
8339 && (board[toY][toX] == EmptySquare)) {
8340 board[fromY][fromX] = EmptySquare;
8341 board[toY][toX] = WhitePawn;
8342 if(oldEP & EP_BEROLIN_A) {
8343 captured = board[fromY][fromX-1];
8344 board[fromY][fromX-1] = EmptySquare;
8345 }else{ captured = board[fromY][fromX+1];
8346 board[fromY][fromX+1] = EmptySquare;
8348 } else if (board[fromY][fromX] == king
8349 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8350 && toY == fromY && toX > fromX+1) {
8351 board[fromY][fromX] = EmptySquare;
8352 board[toY][toX] = king;
8353 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8354 board[fromY][BOARD_RGHT-1] = EmptySquare;
8355 } else if (board[fromY][fromX] == king
8356 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8357 && toY == fromY && toX < fromX-1) {
8358 board[fromY][fromX] = EmptySquare;
8359 board[toY][toX] = king;
8360 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8361 board[fromY][BOARD_LEFT] = EmptySquare;
8362 } else if (fromY == 7 && fromX == 3
8363 && board[fromY][fromX] == BlackKing
8364 && toY == 7 && toX == 5) {
8365 board[fromY][fromX] = EmptySquare;
8366 board[toY][toX] = BlackKing;
8367 board[fromY][7] = EmptySquare;
8368 board[toY][4] = BlackRook;
8369 } else if (fromY == 7 && fromX == 3
8370 && board[fromY][fromX] == BlackKing
8371 && toY == 7 && toX == 1) {
8372 board[fromY][fromX] = EmptySquare;
8373 board[toY][toX] = BlackKing;
8374 board[fromY][0] = EmptySquare;
8375 board[toY][2] = BlackRook;
8376 } else if (board[fromY][fromX] == BlackPawn
8378 && gameInfo.variant != VariantXiangqi
8380 /* black pawn promotion */
8381 board[toY][toX] = CharToPiece(ToLower(promoChar));
8382 if (board[toY][toX] == EmptySquare) {
8383 board[toY][toX] = BlackQueen;
8385 if(gameInfo.variant==VariantBughouse ||
8386 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8387 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8388 board[fromY][fromX] = EmptySquare;
8389 } else if ((fromY == 3)
8391 && gameInfo.variant != VariantXiangqi
8392 && gameInfo.variant != VariantBerolina
8393 && (board[fromY][fromX] == BlackPawn)
8394 && (board[toY][toX] == EmptySquare)) {
8395 board[fromY][fromX] = EmptySquare;
8396 board[toY][toX] = BlackPawn;
8397 captured = board[toY + 1][toX];
8398 board[toY + 1][toX] = EmptySquare;
8399 } else if ((fromY == 3)
8401 && gameInfo.variant == VariantBerolina
8402 && (board[fromY][fromX] == BlackPawn)
8403 && (board[toY][toX] == EmptySquare)) {
8404 board[fromY][fromX] = EmptySquare;
8405 board[toY][toX] = BlackPawn;
8406 if(oldEP & EP_BEROLIN_A) {
8407 captured = board[fromY][fromX-1];
8408 board[fromY][fromX-1] = EmptySquare;
8409 }else{ captured = board[fromY][fromX+1];
8410 board[fromY][fromX+1] = EmptySquare;
8413 board[toY][toX] = board[fromY][fromX];
8414 board[fromY][fromX] = EmptySquare;
8417 /* [HGM] now we promote for Shogi, if needed */
8418 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8419 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8422 if (gameInfo.holdingsWidth != 0) {
8424 /* !!A lot more code needs to be written to support holdings */
8425 /* [HGM] OK, so I have written it. Holdings are stored in the */
8426 /* penultimate board files, so they are automaticlly stored */
8427 /* in the game history. */
8428 if (fromY == DROP_RANK) {
8429 /* Delete from holdings, by decreasing count */
8430 /* and erasing image if necessary */
8432 if(p < (int) BlackPawn) { /* white drop */
8433 p -= (int)WhitePawn;
8434 p = PieceToNumber((ChessSquare)p);
8435 if(p >= gameInfo.holdingsSize) p = 0;
8436 if(--board[p][BOARD_WIDTH-2] <= 0)
8437 board[p][BOARD_WIDTH-1] = EmptySquare;
8438 if((int)board[p][BOARD_WIDTH-2] < 0)
8439 board[p][BOARD_WIDTH-2] = 0;
8440 } else { /* black drop */
8441 p -= (int)BlackPawn;
8442 p = PieceToNumber((ChessSquare)p);
8443 if(p >= gameInfo.holdingsSize) p = 0;
8444 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8445 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8446 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8447 board[BOARD_HEIGHT-1-p][1] = 0;
8450 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8451 && gameInfo.variant != VariantBughouse ) {
8452 /* [HGM] holdings: Add to holdings, if holdings exist */
8453 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8454 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8455 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8458 if (p >= (int) BlackPawn) {
8459 p -= (int)BlackPawn;
8460 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8461 /* in Shogi restore piece to its original first */
8462 captured = (ChessSquare) (DEMOTED captured);
8465 p = PieceToNumber((ChessSquare)p);
8466 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8467 board[p][BOARD_WIDTH-2]++;
8468 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8470 p -= (int)WhitePawn;
8471 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8472 captured = (ChessSquare) (DEMOTED captured);
8475 p = PieceToNumber((ChessSquare)p);
8476 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8477 board[BOARD_HEIGHT-1-p][1]++;
8478 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8481 } else if (gameInfo.variant == VariantAtomic) {
8482 if (captured != EmptySquare) {
8484 for (y = toY-1; y <= toY+1; y++) {
8485 for (x = toX-1; x <= toX+1; x++) {
8486 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8487 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8488 board[y][x] = EmptySquare;
8492 board[toY][toX] = EmptySquare;
8495 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8496 /* [HGM] Shogi promotions */
8497 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8500 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8501 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8502 // [HGM] superchess: take promotion piece out of holdings
8503 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8504 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8505 if(!--board[k][BOARD_WIDTH-2])
8506 board[k][BOARD_WIDTH-1] = EmptySquare;
8508 if(!--board[BOARD_HEIGHT-1-k][1])
8509 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8515 /* Updates forwardMostMove */
8517 MakeMove(fromX, fromY, toX, toY, promoChar)
8518 int fromX, fromY, toX, toY;
8521 // forwardMostMove++; // [HGM] bare: moved downstream
8523 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8524 int timeLeft; static int lastLoadFlag=0; int king, piece;
8525 piece = boards[forwardMostMove][fromY][fromX];
8526 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8527 if(gameInfo.variant == VariantKnightmate)
8528 king += (int) WhiteUnicorn - (int) WhiteKing;
8529 if(forwardMostMove == 0) {
8531 fprintf(serverMoves, "%s;", second.tidy);
8532 fprintf(serverMoves, "%s;", first.tidy);
8533 if(!blackPlaysFirst)
8534 fprintf(serverMoves, "%s;", second.tidy);
8535 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8536 lastLoadFlag = loadFlag;
8538 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8539 // print castling suffix
8540 if( toY == fromY && piece == king ) {
8542 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8544 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8547 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8548 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8549 boards[forwardMostMove][toY][toX] == EmptySquare
8551 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8553 if(promoChar != NULLCHAR)
8554 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8556 fprintf(serverMoves, "/%d/%d",
8557 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8558 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8559 else timeLeft = blackTimeRemaining/1000;
8560 fprintf(serverMoves, "/%d", timeLeft);
8562 fflush(serverMoves);
8565 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8566 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8570 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8571 if (commentList[forwardMostMove+1] != NULL) {
8572 free(commentList[forwardMostMove+1]);
8573 commentList[forwardMostMove+1] = NULL;
8575 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8576 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8577 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8578 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8579 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8580 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8581 gameInfo.result = GameUnfinished;
8582 if (gameInfo.resultDetails != NULL) {
8583 free(gameInfo.resultDetails);
8584 gameInfo.resultDetails = NULL;
8586 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8587 moveList[forwardMostMove - 1]);
8588 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8589 PosFlags(forwardMostMove - 1),
8590 fromY, fromX, toY, toX, promoChar,
8591 parseList[forwardMostMove - 1]);
8592 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8598 if(gameInfo.variant != VariantShogi)
8599 strcat(parseList[forwardMostMove - 1], "+");
8603 strcat(parseList[forwardMostMove - 1], "#");
8606 if (appData.debugMode) {
8607 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8612 /* Updates currentMove if not pausing */
8614 ShowMove(fromX, fromY, toX, toY)
8616 int instant = (gameMode == PlayFromGameFile) ?
8617 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8618 if(appData.noGUI) return;
8619 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8621 if (forwardMostMove == currentMove + 1) {
8622 AnimateMove(boards[forwardMostMove - 1],
8623 fromX, fromY, toX, toY);
8625 if (appData.highlightLastMove) {
8626 SetHighlights(fromX, fromY, toX, toY);
8629 currentMove = forwardMostMove;
8632 if (instant) return;
8634 DisplayMove(currentMove - 1);
8635 DrawPosition(FALSE, boards[currentMove]);
8636 DisplayBothClocks();
8637 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8640 void SendEgtPath(ChessProgramState *cps)
8641 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8642 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8644 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8647 char c, *q = name+1, *r, *s;
8649 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8650 while(*p && *p != ',') *q++ = *p++;
8652 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8653 strcmp(name, ",nalimov:") == 0 ) {
8654 // take nalimov path from the menu-changeable option first, if it is defined
8655 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8656 SendToProgram(buf,cps); // send egtbpath command for nalimov
8658 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8659 (s = StrStr(appData.egtFormats, name)) != NULL) {
8660 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8661 s = r = StrStr(s, ":") + 1; // beginning of path info
8662 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8663 c = *r; *r = 0; // temporarily null-terminate path info
8664 *--q = 0; // strip of trailig ':' from name
8665 sprintf(buf, "egtpath %s %s\n", name+1, s);
8667 SendToProgram(buf,cps); // send egtbpath command for this format
8669 if(*p == ',') p++; // read away comma to position for next format name
8674 InitChessProgram(cps, setup)
8675 ChessProgramState *cps;
8676 int setup; /* [HGM] needed to setup FRC opening position */
8678 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8679 if (appData.noChessProgram) return;
8680 hintRequested = FALSE;
8681 bookRequested = FALSE;
8683 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8684 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8685 if(cps->memSize) { /* [HGM] memory */
8686 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8687 SendToProgram(buf, cps);
8689 SendEgtPath(cps); /* [HGM] EGT */
8690 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8691 sprintf(buf, "cores %d\n", appData.smpCores);
8692 SendToProgram(buf, cps);
8695 SendToProgram(cps->initString, cps);
8696 if (gameInfo.variant != VariantNormal &&
8697 gameInfo.variant != VariantLoadable
8698 /* [HGM] also send variant if board size non-standard */
8699 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8701 char *v = VariantName(gameInfo.variant);
8702 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8703 /* [HGM] in protocol 1 we have to assume all variants valid */
8704 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8705 DisplayFatalError(buf, 0, 1);
8709 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8710 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8711 if( gameInfo.variant == VariantXiangqi )
8712 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8713 if( gameInfo.variant == VariantShogi )
8714 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8715 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8716 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8717 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8718 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8719 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8720 if( gameInfo.variant == VariantCourier )
8721 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8722 if( gameInfo.variant == VariantSuper )
8723 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8724 if( gameInfo.variant == VariantGreat )
8725 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8728 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8729 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8730 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8731 if(StrStr(cps->variants, b) == NULL) {
8732 // specific sized variant not known, check if general sizing allowed
8733 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8734 if(StrStr(cps->variants, "boardsize") == NULL) {
8735 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8736 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8737 DisplayFatalError(buf, 0, 1);
8740 /* [HGM] here we really should compare with the maximum supported board size */
8743 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8744 sprintf(buf, "variant %s\n", b);
8745 SendToProgram(buf, cps);
8747 currentlyInitializedVariant = gameInfo.variant;
8749 /* [HGM] send opening position in FRC to first engine */
8751 SendToProgram("force\n", cps);
8753 /* engine is now in force mode! Set flag to wake it up after first move. */
8754 setboardSpoiledMachineBlack = 1;
8758 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8759 SendToProgram(buf, cps);
8761 cps->maybeThinking = FALSE;
8762 cps->offeredDraw = 0;
8763 if (!appData.icsActive) {
8764 SendTimeControl(cps, movesPerSession, timeControl,
8765 timeIncrement, appData.searchDepth,
8768 if (appData.showThinking
8769 // [HGM] thinking: four options require thinking output to be sent
8770 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8772 SendToProgram("post\n", cps);
8774 SendToProgram("hard\n", cps);
8775 if (!appData.ponderNextMove) {
8776 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8777 it without being sure what state we are in first. "hard"
8778 is not a toggle, so that one is OK.
8780 SendToProgram("easy\n", cps);
8783 sprintf(buf, "ping %d\n", ++cps->lastPing);
8784 SendToProgram(buf, cps);
8786 cps->initDone = TRUE;
8791 StartChessProgram(cps)
8792 ChessProgramState *cps;
8797 if (appData.noChessProgram) return;
8798 cps->initDone = FALSE;
8800 if (strcmp(cps->host, "localhost") == 0) {
8801 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8802 } else if (*appData.remoteShell == NULLCHAR) {
8803 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8805 if (*appData.remoteUser == NULLCHAR) {
8806 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8809 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8810 cps->host, appData.remoteUser, cps->program);
8812 err = StartChildProcess(buf, "", &cps->pr);
8816 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8817 DisplayFatalError(buf, err, 1);
8823 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8824 if (cps->protocolVersion > 1) {
8825 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8826 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8827 cps->comboCnt = 0; // and values of combo boxes
8828 SendToProgram(buf, cps);
8830 SendToProgram("xboard\n", cps);
8836 TwoMachinesEventIfReady P((void))
8838 if (first.lastPing != first.lastPong) {
8839 DisplayMessage("", _("Waiting for first chess program"));
8840 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8843 if (second.lastPing != second.lastPong) {
8844 DisplayMessage("", _("Waiting for second chess program"));
8845 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8853 NextMatchGame P((void))
8855 int index; /* [HGM] autoinc: step load index during match */
8857 if (*appData.loadGameFile != NULLCHAR) {
8858 index = appData.loadGameIndex;
8859 if(index < 0) { // [HGM] autoinc
8860 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8861 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8863 LoadGameFromFile(appData.loadGameFile,
8865 appData.loadGameFile, FALSE);
8866 } else if (*appData.loadPositionFile != NULLCHAR) {
8867 index = appData.loadPositionIndex;
8868 if(index < 0) { // [HGM] autoinc
8869 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8870 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8872 LoadPositionFromFile(appData.loadPositionFile,
8874 appData.loadPositionFile);
8876 TwoMachinesEventIfReady();
8879 void UserAdjudicationEvent( int result )
8881 ChessMove gameResult = GameIsDrawn;
8884 gameResult = WhiteWins;
8886 else if( result < 0 ) {
8887 gameResult = BlackWins;
8890 if( gameMode == TwoMachinesPlay ) {
8891 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8896 // [HGM] save: calculate checksum of game to make games easily identifiable
8897 int StringCheckSum(char *s)
8900 if(s==NULL) return 0;
8901 while(*s) i = i*259 + *s++;
8908 for(i=backwardMostMove; i<forwardMostMove; i++) {
8909 sum += pvInfoList[i].depth;
8910 sum += StringCheckSum(parseList[i]);
8911 sum += StringCheckSum(commentList[i]);
8914 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8915 return sum + StringCheckSum(commentList[i]);
8916 } // end of save patch
8919 GameEnds(result, resultDetails, whosays)
8921 char *resultDetails;
8924 GameMode nextGameMode;
8928 if(endingGame) return; /* [HGM] crash: forbid recursion */
8930 if(twoBoards) { // [HGM] dual: switch back to one board
8931 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8932 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8934 if (appData.debugMode) {
8935 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8936 result, resultDetails ? resultDetails : "(null)", whosays);
8939 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8941 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8942 /* If we are playing on ICS, the server decides when the
8943 game is over, but the engine can offer to draw, claim
8947 if (appData.zippyPlay && first.initDone) {
8948 if (result == GameIsDrawn) {
8949 /* In case draw still needs to be claimed */
8950 SendToICS(ics_prefix);
8951 SendToICS("draw\n");
8952 } else if (StrCaseStr(resultDetails, "resign")) {
8953 SendToICS(ics_prefix);
8954 SendToICS("resign\n");
8958 endingGame = 0; /* [HGM] crash */
8962 /* If we're loading the game from a file, stop */
8963 if (whosays == GE_FILE) {
8964 (void) StopLoadGameTimer();
8968 /* Cancel draw offers */
8969 first.offeredDraw = second.offeredDraw = 0;
8971 /* If this is an ICS game, only ICS can really say it's done;
8972 if not, anyone can. */
8973 isIcsGame = (gameMode == IcsPlayingWhite ||
8974 gameMode == IcsPlayingBlack ||
8975 gameMode == IcsObserving ||
8976 gameMode == IcsExamining);
8978 if (!isIcsGame || whosays == GE_ICS) {
8979 /* OK -- not an ICS game, or ICS said it was done */
8981 if (!isIcsGame && !appData.noChessProgram)
8982 SetUserThinkingEnables();
8984 /* [HGM] if a machine claims the game end we verify this claim */
8985 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8986 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8988 ChessMove trueResult = (ChessMove) -1;
8990 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8991 first.twoMachinesColor[0] :
8992 second.twoMachinesColor[0] ;
8994 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8995 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8996 /* [HGM] verify: engine mate claims accepted if they were flagged */
8997 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8999 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9000 /* [HGM] verify: engine mate claims accepted if they were flagged */
9001 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9003 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9004 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9007 // now verify win claims, but not in drop games, as we don't understand those yet
9008 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9009 || gameInfo.variant == VariantGreat) &&
9010 (result == WhiteWins && claimer == 'w' ||
9011 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9012 if (appData.debugMode) {
9013 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9014 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9016 if(result != trueResult) {
9017 sprintf(buf, "False win claim: '%s'", resultDetails);
9018 result = claimer == 'w' ? BlackWins : WhiteWins;
9019 resultDetails = buf;
9022 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9023 && (forwardMostMove <= backwardMostMove ||
9024 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9025 (claimer=='b')==(forwardMostMove&1))
9027 /* [HGM] verify: draws that were not flagged are false claims */
9028 sprintf(buf, "False draw claim: '%s'", resultDetails);
9029 result = claimer == 'w' ? BlackWins : WhiteWins;
9030 resultDetails = buf;
9032 /* (Claiming a loss is accepted no questions asked!) */
9034 /* [HGM] bare: don't allow bare King to win */
9035 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9036 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9037 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9038 && result != GameIsDrawn)
9039 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9040 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9041 int p = (signed char)boards[forwardMostMove][i][j] - color;
9042 if(p >= 0 && p <= (int)WhiteKing) k++;
9044 if (appData.debugMode) {
9045 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9046 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9049 result = GameIsDrawn;
9050 sprintf(buf, "%s but bare king", resultDetails);
9051 resultDetails = buf;
9057 if(serverMoves != NULL && !loadFlag) { char c = '=';
9058 if(result==WhiteWins) c = '+';
9059 if(result==BlackWins) c = '-';
9060 if(resultDetails != NULL)
9061 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9063 if (resultDetails != NULL) {
9064 gameInfo.result = result;
9065 gameInfo.resultDetails = StrSave(resultDetails);
9067 /* display last move only if game was not loaded from file */
9068 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9069 DisplayMove(currentMove - 1);
9071 if (forwardMostMove != 0) {
9072 if (gameMode != PlayFromGameFile && gameMode != EditGame
9073 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9075 if (*appData.saveGameFile != NULLCHAR) {
9076 SaveGameToFile(appData.saveGameFile, TRUE);
9077 } else if (appData.autoSaveGames) {
9080 if (*appData.savePositionFile != NULLCHAR) {
9081 SavePositionToFile(appData.savePositionFile);
9086 /* Tell program how game ended in case it is learning */
9087 /* [HGM] Moved this to after saving the PGN, just in case */
9088 /* engine died and we got here through time loss. In that */
9089 /* case we will get a fatal error writing the pipe, which */
9090 /* would otherwise lose us the PGN. */
9091 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9092 /* output during GameEnds should never be fatal anymore */
9093 if (gameMode == MachinePlaysWhite ||
9094 gameMode == MachinePlaysBlack ||
9095 gameMode == TwoMachinesPlay ||
9096 gameMode == IcsPlayingWhite ||
9097 gameMode == IcsPlayingBlack ||
9098 gameMode == BeginningOfGame) {
9100 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9102 if (first.pr != NoProc) {
9103 SendToProgram(buf, &first);
9105 if (second.pr != NoProc &&
9106 gameMode == TwoMachinesPlay) {
9107 SendToProgram(buf, &second);
9112 if (appData.icsActive) {
9113 if (appData.quietPlay &&
9114 (gameMode == IcsPlayingWhite ||
9115 gameMode == IcsPlayingBlack)) {
9116 SendToICS(ics_prefix);
9117 SendToICS("set shout 1\n");
9119 nextGameMode = IcsIdle;
9120 ics_user_moved = FALSE;
9121 /* clean up premove. It's ugly when the game has ended and the
9122 * premove highlights are still on the board.
9126 ClearPremoveHighlights();
9127 DrawPosition(FALSE, boards[currentMove]);
9129 if (whosays == GE_ICS) {
9132 if (gameMode == IcsPlayingWhite)
9134 else if(gameMode == IcsPlayingBlack)
9138 if (gameMode == IcsPlayingBlack)
9140 else if(gameMode == IcsPlayingWhite)
9147 PlayIcsUnfinishedSound();
9150 } else if (gameMode == EditGame ||
9151 gameMode == PlayFromGameFile ||
9152 gameMode == AnalyzeMode ||
9153 gameMode == AnalyzeFile) {
9154 nextGameMode = gameMode;
9156 nextGameMode = EndOfGame;
9161 nextGameMode = gameMode;
9164 if (appData.noChessProgram) {
9165 gameMode = nextGameMode;
9167 endingGame = 0; /* [HGM] crash */
9172 /* Put first chess program into idle state */
9173 if (first.pr != NoProc &&
9174 (gameMode == MachinePlaysWhite ||
9175 gameMode == MachinePlaysBlack ||
9176 gameMode == TwoMachinesPlay ||
9177 gameMode == IcsPlayingWhite ||
9178 gameMode == IcsPlayingBlack ||
9179 gameMode == BeginningOfGame)) {
9180 SendToProgram("force\n", &first);
9181 if (first.usePing) {
9183 sprintf(buf, "ping %d\n", ++first.lastPing);
9184 SendToProgram(buf, &first);
9187 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9188 /* Kill off first chess program */
9189 if (first.isr != NULL)
9190 RemoveInputSource(first.isr);
9193 if (first.pr != NoProc) {
9195 DoSleep( appData.delayBeforeQuit );
9196 SendToProgram("quit\n", &first);
9197 DoSleep( appData.delayAfterQuit );
9198 DestroyChildProcess(first.pr, first.useSigterm);
9203 /* Put second chess program into idle state */
9204 if (second.pr != NoProc &&
9205 gameMode == TwoMachinesPlay) {
9206 SendToProgram("force\n", &second);
9207 if (second.usePing) {
9209 sprintf(buf, "ping %d\n", ++second.lastPing);
9210 SendToProgram(buf, &second);
9213 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9214 /* Kill off second chess program */
9215 if (second.isr != NULL)
9216 RemoveInputSource(second.isr);
9219 if (second.pr != NoProc) {
9220 DoSleep( appData.delayBeforeQuit );
9221 SendToProgram("quit\n", &second);
9222 DoSleep( appData.delayAfterQuit );
9223 DestroyChildProcess(second.pr, second.useSigterm);
9228 if (matchMode && gameMode == TwoMachinesPlay) {
9231 if (first.twoMachinesColor[0] == 'w') {
9238 if (first.twoMachinesColor[0] == 'b') {
9247 if (matchGame < appData.matchGames) {
9249 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9250 tmp = first.twoMachinesColor;
9251 first.twoMachinesColor = second.twoMachinesColor;
9252 second.twoMachinesColor = tmp;
9254 gameMode = nextGameMode;
9256 if(appData.matchPause>10000 || appData.matchPause<10)
9257 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9258 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9259 endingGame = 0; /* [HGM] crash */
9263 gameMode = nextGameMode;
9264 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9265 first.tidy, second.tidy,
9266 first.matchWins, second.matchWins,
9267 appData.matchGames - (first.matchWins + second.matchWins));
9268 DisplayFatalError(buf, 0, 0);
9271 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9272 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9274 gameMode = nextGameMode;
9276 endingGame = 0; /* [HGM] crash */
9279 /* Assumes program was just initialized (initString sent).
9280 Leaves program in force mode. */
9282 FeedMovesToProgram(cps, upto)
9283 ChessProgramState *cps;
9288 if (appData.debugMode)
9289 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9290 startedFromSetupPosition ? "position and " : "",
9291 backwardMostMove, upto, cps->which);
9292 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9293 // [HGM] variantswitch: make engine aware of new variant
9294 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9295 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9296 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9297 SendToProgram(buf, cps);
9298 currentlyInitializedVariant = gameInfo.variant;
9300 SendToProgram("force\n", cps);
9301 if (startedFromSetupPosition) {
9302 SendBoard(cps, backwardMostMove);
9303 if (appData.debugMode) {
9304 fprintf(debugFP, "feedMoves\n");
9307 for (i = backwardMostMove; i < upto; i++) {
9308 SendMoveToProgram(i, cps);
9314 ResurrectChessProgram()
9316 /* The chess program may have exited.
9317 If so, restart it and feed it all the moves made so far. */
9319 if (appData.noChessProgram || first.pr != NoProc) return;
9321 StartChessProgram(&first);
9322 InitChessProgram(&first, FALSE);
9323 FeedMovesToProgram(&first, currentMove);
9325 if (!first.sendTime) {
9326 /* can't tell gnuchess what its clock should read,
9327 so we bow to its notion. */
9329 timeRemaining[0][currentMove] = whiteTimeRemaining;
9330 timeRemaining[1][currentMove] = blackTimeRemaining;
9333 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9334 appData.icsEngineAnalyze) && first.analysisSupport) {
9335 SendToProgram("analyze\n", &first);
9336 first.analyzing = TRUE;
9349 if (appData.debugMode) {
9350 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9351 redraw, init, gameMode);
9353 CleanupTail(); // [HGM] vari: delete any stored variations
9354 pausing = pauseExamInvalid = FALSE;
9355 startedFromSetupPosition = blackPlaysFirst = FALSE;
9357 whiteFlag = blackFlag = FALSE;
9358 userOfferedDraw = FALSE;
9359 hintRequested = bookRequested = FALSE;
9360 first.maybeThinking = FALSE;
9361 second.maybeThinking = FALSE;
9362 first.bookSuspend = FALSE; // [HGM] book
9363 second.bookSuspend = FALSE;
9364 thinkOutput[0] = NULLCHAR;
9365 lastHint[0] = NULLCHAR;
9366 ClearGameInfo(&gameInfo);
9367 gameInfo.variant = StringToVariant(appData.variant);
9368 ics_user_moved = ics_clock_paused = FALSE;
9369 ics_getting_history = H_FALSE;
9371 white_holding[0] = black_holding[0] = NULLCHAR;
9372 ClearProgramStats();
9373 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9377 flipView = appData.flipView;
9378 ClearPremoveHighlights();
9380 alarmSounded = FALSE;
9382 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9383 if(appData.serverMovesName != NULL) {
9384 /* [HGM] prepare to make moves file for broadcasting */
9385 clock_t t = clock();
9386 if(serverMoves != NULL) fclose(serverMoves);
9387 serverMoves = fopen(appData.serverMovesName, "r");
9388 if(serverMoves != NULL) {
9389 fclose(serverMoves);
9390 /* delay 15 sec before overwriting, so all clients can see end */
9391 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9393 serverMoves = fopen(appData.serverMovesName, "w");
9397 gameMode = BeginningOfGame;
9399 if(appData.icsActive) gameInfo.variant = VariantNormal;
9400 currentMove = forwardMostMove = backwardMostMove = 0;
9401 InitPosition(redraw);
9402 for (i = 0; i < MAX_MOVES; i++) {
9403 if (commentList[i] != NULL) {
9404 free(commentList[i]);
9405 commentList[i] = NULL;
9409 timeRemaining[0][0] = whiteTimeRemaining;
9410 timeRemaining[1][0] = blackTimeRemaining;
9411 if (first.pr == NULL) {
9412 StartChessProgram(&first);
9415 InitChessProgram(&first, startedFromSetupPosition);
9418 DisplayMessage("", "");
9419 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9420 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9427 if (!AutoPlayOneMove())
9429 if (matchMode || appData.timeDelay == 0)
9431 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9433 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9442 int fromX, fromY, toX, toY;
9444 if (appData.debugMode) {
9445 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9448 if (gameMode != PlayFromGameFile)
9451 if (currentMove >= forwardMostMove) {
9452 gameMode = EditGame;
9455 /* [AS] Clear current move marker at the end of a game */
9456 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9461 toX = moveList[currentMove][2] - AAA;
9462 toY = moveList[currentMove][3] - ONE;
9464 if (moveList[currentMove][1] == '@') {
9465 if (appData.highlightLastMove) {
9466 SetHighlights(-1, -1, toX, toY);
9469 fromX = moveList[currentMove][0] - AAA;
9470 fromY = moveList[currentMove][1] - ONE;
9472 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9474 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9476 if (appData.highlightLastMove) {
9477 SetHighlights(fromX, fromY, toX, toY);
9480 DisplayMove(currentMove);
9481 SendMoveToProgram(currentMove++, &first);
9482 DisplayBothClocks();
9483 DrawPosition(FALSE, boards[currentMove]);
9484 // [HGM] PV info: always display, routine tests if empty
9485 DisplayComment(currentMove - 1, commentList[currentMove]);
9491 LoadGameOneMove(readAhead)
9492 ChessMove readAhead;
9494 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9495 char promoChar = NULLCHAR;
9500 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9501 gameMode != AnalyzeMode && gameMode != Training) {
9506 yyboardindex = forwardMostMove;
9507 if (readAhead != (ChessMove)0) {
9508 moveType = readAhead;
9510 if (gameFileFP == NULL)
9512 moveType = (ChessMove) yylex();
9518 if (appData.debugMode)
9519 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9522 /* append the comment but don't display it */
9523 AppendComment(currentMove, p, FALSE);
9526 case WhiteCapturesEnPassant:
9527 case BlackCapturesEnPassant:
9528 case WhitePromotionChancellor:
9529 case BlackPromotionChancellor:
9530 case WhitePromotionArchbishop:
9531 case BlackPromotionArchbishop:
9532 case WhitePromotionCentaur:
9533 case BlackPromotionCentaur:
9534 case WhitePromotionQueen:
9535 case BlackPromotionQueen:
9536 case WhitePromotionRook:
9537 case BlackPromotionRook:
9538 case WhitePromotionBishop:
9539 case BlackPromotionBishop:
9540 case WhitePromotionKnight:
9541 case BlackPromotionKnight:
9542 case WhitePromotionKing:
9543 case BlackPromotionKing:
9545 case WhiteKingSideCastle:
9546 case WhiteQueenSideCastle:
9547 case BlackKingSideCastle:
9548 case BlackQueenSideCastle:
9549 case WhiteKingSideCastleWild:
9550 case WhiteQueenSideCastleWild:
9551 case BlackKingSideCastleWild:
9552 case BlackQueenSideCastleWild:
9554 case WhiteHSideCastleFR:
9555 case WhiteASideCastleFR:
9556 case BlackHSideCastleFR:
9557 case BlackASideCastleFR:
9559 if (appData.debugMode)
9560 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9561 fromX = currentMoveString[0] - AAA;
9562 fromY = currentMoveString[1] - ONE;
9563 toX = currentMoveString[2] - AAA;
9564 toY = currentMoveString[3] - ONE;
9565 promoChar = currentMoveString[4];
9570 if (appData.debugMode)
9571 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9572 fromX = moveType == WhiteDrop ?
9573 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9574 (int) CharToPiece(ToLower(currentMoveString[0]));
9576 toX = currentMoveString[2] - AAA;
9577 toY = currentMoveString[3] - ONE;
9583 case GameUnfinished:
9584 if (appData.debugMode)
9585 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9586 p = strchr(yy_text, '{');
9587 if (p == NULL) p = strchr(yy_text, '(');
9590 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9592 q = strchr(p, *p == '{' ? '}' : ')');
9593 if (q != NULL) *q = NULLCHAR;
9596 GameEnds(moveType, p, GE_FILE);
9598 if (cmailMsgLoaded) {
9600 flipView = WhiteOnMove(currentMove);
9601 if (moveType == GameUnfinished) flipView = !flipView;
9602 if (appData.debugMode)
9603 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9607 case (ChessMove) 0: /* end of file */
9608 if (appData.debugMode)
9609 fprintf(debugFP, "Parser hit end of file\n");
9610 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9616 if (WhiteOnMove(currentMove)) {
9617 GameEnds(BlackWins, "Black mates", GE_FILE);
9619 GameEnds(WhiteWins, "White mates", GE_FILE);
9623 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9630 if (lastLoadGameStart == GNUChessGame) {
9631 /* GNUChessGames have numbers, but they aren't move numbers */
9632 if (appData.debugMode)
9633 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9634 yy_text, (int) moveType);
9635 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9637 /* else fall thru */
9642 /* Reached start of next game in file */
9643 if (appData.debugMode)
9644 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9645 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9651 if (WhiteOnMove(currentMove)) {
9652 GameEnds(BlackWins, "Black mates", GE_FILE);
9654 GameEnds(WhiteWins, "White mates", GE_FILE);
9658 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9664 case PositionDiagram: /* should not happen; ignore */
9665 case ElapsedTime: /* ignore */
9666 case NAG: /* ignore */
9667 if (appData.debugMode)
9668 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9669 yy_text, (int) moveType);
9670 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9673 if (appData.testLegality) {
9674 if (appData.debugMode)
9675 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9676 sprintf(move, _("Illegal move: %d.%s%s"),
9677 (forwardMostMove / 2) + 1,
9678 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9679 DisplayError(move, 0);
9682 if (appData.debugMode)
9683 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9684 yy_text, currentMoveString);
9685 fromX = currentMoveString[0] - AAA;
9686 fromY = currentMoveString[1] - ONE;
9687 toX = currentMoveString[2] - AAA;
9688 toY = currentMoveString[3] - ONE;
9689 promoChar = currentMoveString[4];
9694 if (appData.debugMode)
9695 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9696 sprintf(move, _("Ambiguous move: %d.%s%s"),
9697 (forwardMostMove / 2) + 1,
9698 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9699 DisplayError(move, 0);
9704 case ImpossibleMove:
9705 if (appData.debugMode)
9706 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9707 sprintf(move, _("Illegal move: %d.%s%s"),
9708 (forwardMostMove / 2) + 1,
9709 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9710 DisplayError(move, 0);
9716 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9717 DrawPosition(FALSE, boards[currentMove]);
9718 DisplayBothClocks();
9719 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9720 DisplayComment(currentMove - 1, commentList[currentMove]);
9722 (void) StopLoadGameTimer();
9724 cmailOldMove = forwardMostMove;
9727 /* currentMoveString is set as a side-effect of yylex */
9728 strcat(currentMoveString, "\n");
9729 strcpy(moveList[forwardMostMove], currentMoveString);
9731 thinkOutput[0] = NULLCHAR;
9732 MakeMove(fromX, fromY, toX, toY, promoChar);
9733 currentMove = forwardMostMove;
9738 /* Load the nth game from the given file */
9740 LoadGameFromFile(filename, n, title, useList)
9744 /*Boolean*/ int useList;
9749 if (strcmp(filename, "-") == 0) {
9753 f = fopen(filename, "rb");
9755 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9756 DisplayError(buf, errno);
9760 if (fseek(f, 0, 0) == -1) {
9761 /* f is not seekable; probably a pipe */
9764 if (useList && n == 0) {
9765 int error = GameListBuild(f);
9767 DisplayError(_("Cannot build game list"), error);
9768 } else if (!ListEmpty(&gameList) &&
9769 ((ListGame *) gameList.tailPred)->number > 1) {
9770 GameListPopUp(f, title);
9777 return LoadGame(f, n, title, FALSE);
9782 MakeRegisteredMove()
9784 int fromX, fromY, toX, toY;
9786 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9787 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9790 if (appData.debugMode)
9791 fprintf(debugFP, "Restoring %s for game %d\n",
9792 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9794 thinkOutput[0] = NULLCHAR;
9795 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9796 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9797 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9798 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9799 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9800 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9801 MakeMove(fromX, fromY, toX, toY, promoChar);
9802 ShowMove(fromX, fromY, toX, toY);
9804 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9811 if (WhiteOnMove(currentMove)) {
9812 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9814 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9819 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9826 if (WhiteOnMove(currentMove)) {
9827 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9829 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9834 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9845 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9847 CmailLoadGame(f, gameNumber, title, useList)
9855 if (gameNumber > nCmailGames) {
9856 DisplayError(_("No more games in this message"), 0);
9859 if (f == lastLoadGameFP) {
9860 int offset = gameNumber - lastLoadGameNumber;
9862 cmailMsg[0] = NULLCHAR;
9863 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9864 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9865 nCmailMovesRegistered--;
9867 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9868 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9869 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9872 if (! RegisterMove()) return FALSE;
9876 retVal = LoadGame(f, gameNumber, title, useList);
9878 /* Make move registered during previous look at this game, if any */
9879 MakeRegisteredMove();
9881 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9882 commentList[currentMove]
9883 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9884 DisplayComment(currentMove - 1, commentList[currentMove]);
9890 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9895 int gameNumber = lastLoadGameNumber + offset;
9896 if (lastLoadGameFP == NULL) {
9897 DisplayError(_("No game has been loaded yet"), 0);
9900 if (gameNumber <= 0) {
9901 DisplayError(_("Can't back up any further"), 0);
9904 if (cmailMsgLoaded) {
9905 return CmailLoadGame(lastLoadGameFP, gameNumber,
9906 lastLoadGameTitle, lastLoadGameUseList);
9908 return LoadGame(lastLoadGameFP, gameNumber,
9909 lastLoadGameTitle, lastLoadGameUseList);
9915 /* Load the nth game from open file f */
9917 LoadGame(f, gameNumber, title, useList)
9925 int gn = gameNumber;
9926 ListGame *lg = NULL;
9929 GameMode oldGameMode;
9930 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9932 if (appData.debugMode)
9933 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9935 if (gameMode == Training )
9936 SetTrainingModeOff();
9938 oldGameMode = gameMode;
9939 if (gameMode != BeginningOfGame) {
9944 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9945 fclose(lastLoadGameFP);
9949 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9952 fseek(f, lg->offset, 0);
9953 GameListHighlight(gameNumber);
9957 DisplayError(_("Game number out of range"), 0);
9962 if (fseek(f, 0, 0) == -1) {
9963 if (f == lastLoadGameFP ?
9964 gameNumber == lastLoadGameNumber + 1 :
9968 DisplayError(_("Can't seek on game file"), 0);
9974 lastLoadGameNumber = gameNumber;
9975 strcpy(lastLoadGameTitle, title);
9976 lastLoadGameUseList = useList;
9980 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9981 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9982 lg->gameInfo.black);
9984 } else if (*title != NULLCHAR) {
9985 if (gameNumber > 1) {
9986 sprintf(buf, "%s %d", title, gameNumber);
9989 DisplayTitle(title);
9993 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9994 gameMode = PlayFromGameFile;
9998 currentMove = forwardMostMove = backwardMostMove = 0;
9999 CopyBoard(boards[0], initialPosition);
10003 * Skip the first gn-1 games in the file.
10004 * Also skip over anything that precedes an identifiable
10005 * start of game marker, to avoid being confused by
10006 * garbage at the start of the file. Currently
10007 * recognized start of game markers are the move number "1",
10008 * the pattern "gnuchess .* game", the pattern
10009 * "^[#;%] [^ ]* game file", and a PGN tag block.
10010 * A game that starts with one of the latter two patterns
10011 * will also have a move number 1, possibly
10012 * following a position diagram.
10013 * 5-4-02: Let's try being more lenient and allowing a game to
10014 * start with an unnumbered move. Does that break anything?
10016 cm = lastLoadGameStart = (ChessMove) 0;
10018 yyboardindex = forwardMostMove;
10019 cm = (ChessMove) yylex();
10021 case (ChessMove) 0:
10022 if (cmailMsgLoaded) {
10023 nCmailGames = CMAIL_MAX_GAMES - gn;
10026 DisplayError(_("Game not found in file"), 0);
10033 lastLoadGameStart = cm;
10036 case MoveNumberOne:
10037 switch (lastLoadGameStart) {
10042 case MoveNumberOne:
10043 case (ChessMove) 0:
10044 gn--; /* count this game */
10045 lastLoadGameStart = cm;
10054 switch (lastLoadGameStart) {
10057 case MoveNumberOne:
10058 case (ChessMove) 0:
10059 gn--; /* count this game */
10060 lastLoadGameStart = cm;
10063 lastLoadGameStart = cm; /* game counted already */
10071 yyboardindex = forwardMostMove;
10072 cm = (ChessMove) yylex();
10073 } while (cm == PGNTag || cm == Comment);
10080 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10081 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10082 != CMAIL_OLD_RESULT) {
10084 cmailResult[ CMAIL_MAX_GAMES
10085 - gn - 1] = CMAIL_OLD_RESULT;
10091 /* Only a NormalMove can be at the start of a game
10092 * without a position diagram. */
10093 if (lastLoadGameStart == (ChessMove) 0) {
10095 lastLoadGameStart = MoveNumberOne;
10104 if (appData.debugMode)
10105 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10107 if (cm == XBoardGame) {
10108 /* Skip any header junk before position diagram and/or move 1 */
10110 yyboardindex = forwardMostMove;
10111 cm = (ChessMove) yylex();
10113 if (cm == (ChessMove) 0 ||
10114 cm == GNUChessGame || cm == XBoardGame) {
10115 /* Empty game; pretend end-of-file and handle later */
10116 cm = (ChessMove) 0;
10120 if (cm == MoveNumberOne || cm == PositionDiagram ||
10121 cm == PGNTag || cm == Comment)
10124 } else if (cm == GNUChessGame) {
10125 if (gameInfo.event != NULL) {
10126 free(gameInfo.event);
10128 gameInfo.event = StrSave(yy_text);
10131 startedFromSetupPosition = FALSE;
10132 while (cm == PGNTag) {
10133 if (appData.debugMode)
10134 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10135 err = ParsePGNTag(yy_text, &gameInfo);
10136 if (!err) numPGNTags++;
10138 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10139 if(gameInfo.variant != oldVariant) {
10140 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10141 InitPosition(TRUE);
10142 oldVariant = gameInfo.variant;
10143 if (appData.debugMode)
10144 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10148 if (gameInfo.fen != NULL) {
10149 Board initial_position;
10150 startedFromSetupPosition = TRUE;
10151 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10153 DisplayError(_("Bad FEN position in file"), 0);
10156 CopyBoard(boards[0], initial_position);
10157 if (blackPlaysFirst) {
10158 currentMove = forwardMostMove = backwardMostMove = 1;
10159 CopyBoard(boards[1], initial_position);
10160 strcpy(moveList[0], "");
10161 strcpy(parseList[0], "");
10162 timeRemaining[0][1] = whiteTimeRemaining;
10163 timeRemaining[1][1] = blackTimeRemaining;
10164 if (commentList[0] != NULL) {
10165 commentList[1] = commentList[0];
10166 commentList[0] = NULL;
10169 currentMove = forwardMostMove = backwardMostMove = 0;
10171 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10173 initialRulePlies = FENrulePlies;
10174 for( i=0; i< nrCastlingRights; i++ )
10175 initialRights[i] = initial_position[CASTLING][i];
10177 yyboardindex = forwardMostMove;
10178 free(gameInfo.fen);
10179 gameInfo.fen = NULL;
10182 yyboardindex = forwardMostMove;
10183 cm = (ChessMove) yylex();
10185 /* Handle comments interspersed among the tags */
10186 while (cm == Comment) {
10188 if (appData.debugMode)
10189 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10191 AppendComment(currentMove, p, FALSE);
10192 yyboardindex = forwardMostMove;
10193 cm = (ChessMove) yylex();
10197 /* don't rely on existence of Event tag since if game was
10198 * pasted from clipboard the Event tag may not exist
10200 if (numPGNTags > 0){
10202 if (gameInfo.variant == VariantNormal) {
10203 VariantClass v = StringToVariant(gameInfo.event);
10204 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10205 if(v < VariantShogi) gameInfo.variant = v;
10208 if( appData.autoDisplayTags ) {
10209 tags = PGNTags(&gameInfo);
10210 TagsPopUp(tags, CmailMsg());
10215 /* Make something up, but don't display it now */
10220 if (cm == PositionDiagram) {
10223 Board initial_position;
10225 if (appData.debugMode)
10226 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10228 if (!startedFromSetupPosition) {
10230 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10231 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10241 initial_position[i][j++] = CharToPiece(*p);
10244 while (*p == ' ' || *p == '\t' ||
10245 *p == '\n' || *p == '\r') p++;
10247 if (strncmp(p, "black", strlen("black"))==0)
10248 blackPlaysFirst = TRUE;
10250 blackPlaysFirst = FALSE;
10251 startedFromSetupPosition = TRUE;
10253 CopyBoard(boards[0], initial_position);
10254 if (blackPlaysFirst) {
10255 currentMove = forwardMostMove = backwardMostMove = 1;
10256 CopyBoard(boards[1], initial_position);
10257 strcpy(moveList[0], "");
10258 strcpy(parseList[0], "");
10259 timeRemaining[0][1] = whiteTimeRemaining;
10260 timeRemaining[1][1] = blackTimeRemaining;
10261 if (commentList[0] != NULL) {
10262 commentList[1] = commentList[0];
10263 commentList[0] = NULL;
10266 currentMove = forwardMostMove = backwardMostMove = 0;
10269 yyboardindex = forwardMostMove;
10270 cm = (ChessMove) yylex();
10273 if (first.pr == NoProc) {
10274 StartChessProgram(&first);
10276 InitChessProgram(&first, FALSE);
10277 SendToProgram("force\n", &first);
10278 if (startedFromSetupPosition) {
10279 SendBoard(&first, forwardMostMove);
10280 if (appData.debugMode) {
10281 fprintf(debugFP, "Load Game\n");
10283 DisplayBothClocks();
10286 /* [HGM] server: flag to write setup moves in broadcast file as one */
10287 loadFlag = appData.suppressLoadMoves;
10289 while (cm == Comment) {
10291 if (appData.debugMode)
10292 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10294 AppendComment(currentMove, p, FALSE);
10295 yyboardindex = forwardMostMove;
10296 cm = (ChessMove) yylex();
10299 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10300 cm == WhiteWins || cm == BlackWins ||
10301 cm == GameIsDrawn || cm == GameUnfinished) {
10302 DisplayMessage("", _("No moves in game"));
10303 if (cmailMsgLoaded) {
10304 if (appData.debugMode)
10305 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10309 DrawPosition(FALSE, boards[currentMove]);
10310 DisplayBothClocks();
10311 gameMode = EditGame;
10318 // [HGM] PV info: routine tests if comment empty
10319 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10320 DisplayComment(currentMove - 1, commentList[currentMove]);
10322 if (!matchMode && appData.timeDelay != 0)
10323 DrawPosition(FALSE, boards[currentMove]);
10325 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10326 programStats.ok_to_send = 1;
10329 /* if the first token after the PGN tags is a move
10330 * and not move number 1, retrieve it from the parser
10332 if (cm != MoveNumberOne)
10333 LoadGameOneMove(cm);
10335 /* load the remaining moves from the file */
10336 while (LoadGameOneMove((ChessMove)0)) {
10337 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10338 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10341 /* rewind to the start of the game */
10342 currentMove = backwardMostMove;
10344 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10346 if (oldGameMode == AnalyzeFile ||
10347 oldGameMode == AnalyzeMode) {
10348 AnalyzeFileEvent();
10351 if (matchMode || appData.timeDelay == 0) {
10353 gameMode = EditGame;
10355 } else if (appData.timeDelay > 0) {
10356 AutoPlayGameLoop();
10359 if (appData.debugMode)
10360 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10362 loadFlag = 0; /* [HGM] true game starts */
10366 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10368 ReloadPosition(offset)
10371 int positionNumber = lastLoadPositionNumber + offset;
10372 if (lastLoadPositionFP == NULL) {
10373 DisplayError(_("No position has been loaded yet"), 0);
10376 if (positionNumber <= 0) {
10377 DisplayError(_("Can't back up any further"), 0);
10380 return LoadPosition(lastLoadPositionFP, positionNumber,
10381 lastLoadPositionTitle);
10384 /* Load the nth position from the given file */
10386 LoadPositionFromFile(filename, n, title)
10394 if (strcmp(filename, "-") == 0) {
10395 return LoadPosition(stdin, n, "stdin");
10397 f = fopen(filename, "rb");
10399 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10400 DisplayError(buf, errno);
10403 return LoadPosition(f, n, title);
10408 /* Load the nth position from the given open file, and close it */
10410 LoadPosition(f, positionNumber, title)
10412 int positionNumber;
10415 char *p, line[MSG_SIZ];
10416 Board initial_position;
10417 int i, j, fenMode, pn;
10419 if (gameMode == Training )
10420 SetTrainingModeOff();
10422 if (gameMode != BeginningOfGame) {
10423 Reset(FALSE, TRUE);
10425 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10426 fclose(lastLoadPositionFP);
10428 if (positionNumber == 0) positionNumber = 1;
10429 lastLoadPositionFP = f;
10430 lastLoadPositionNumber = positionNumber;
10431 strcpy(lastLoadPositionTitle, title);
10432 if (first.pr == NoProc) {
10433 StartChessProgram(&first);
10434 InitChessProgram(&first, FALSE);
10436 pn = positionNumber;
10437 if (positionNumber < 0) {
10438 /* Negative position number means to seek to that byte offset */
10439 if (fseek(f, -positionNumber, 0) == -1) {
10440 DisplayError(_("Can't seek on position file"), 0);
10445 if (fseek(f, 0, 0) == -1) {
10446 if (f == lastLoadPositionFP ?
10447 positionNumber == lastLoadPositionNumber + 1 :
10448 positionNumber == 1) {
10451 DisplayError(_("Can't seek on position file"), 0);
10456 /* See if this file is FEN or old-style xboard */
10457 if (fgets(line, MSG_SIZ, f) == NULL) {
10458 DisplayError(_("Position not found in file"), 0);
10461 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10462 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10465 if (fenMode || line[0] == '#') pn--;
10467 /* skip positions before number pn */
10468 if (fgets(line, MSG_SIZ, f) == NULL) {
10470 DisplayError(_("Position not found in file"), 0);
10473 if (fenMode || line[0] == '#') pn--;
10478 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10479 DisplayError(_("Bad FEN position in file"), 0);
10483 (void) fgets(line, MSG_SIZ, f);
10484 (void) fgets(line, MSG_SIZ, f);
10486 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10487 (void) fgets(line, MSG_SIZ, f);
10488 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10491 initial_position[i][j++] = CharToPiece(*p);
10495 blackPlaysFirst = FALSE;
10497 (void) fgets(line, MSG_SIZ, f);
10498 if (strncmp(line, "black", strlen("black"))==0)
10499 blackPlaysFirst = TRUE;
10502 startedFromSetupPosition = TRUE;
10504 SendToProgram("force\n", &first);
10505 CopyBoard(boards[0], initial_position);
10506 if (blackPlaysFirst) {
10507 currentMove = forwardMostMove = backwardMostMove = 1;
10508 strcpy(moveList[0], "");
10509 strcpy(parseList[0], "");
10510 CopyBoard(boards[1], initial_position);
10511 DisplayMessage("", _("Black to play"));
10513 currentMove = forwardMostMove = backwardMostMove = 0;
10514 DisplayMessage("", _("White to play"));
10516 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10517 SendBoard(&first, forwardMostMove);
10518 if (appData.debugMode) {
10520 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10521 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10522 fprintf(debugFP, "Load Position\n");
10525 if (positionNumber > 1) {
10526 sprintf(line, "%s %d", title, positionNumber);
10527 DisplayTitle(line);
10529 DisplayTitle(title);
10531 gameMode = EditGame;
10534 timeRemaining[0][1] = whiteTimeRemaining;
10535 timeRemaining[1][1] = blackTimeRemaining;
10536 DrawPosition(FALSE, boards[currentMove]);
10543 CopyPlayerNameIntoFileName(dest, src)
10546 while (*src != NULLCHAR && *src != ',') {
10551 *(*dest)++ = *src++;
10556 char *DefaultFileName(ext)
10559 static char def[MSG_SIZ];
10562 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10564 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10566 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10575 /* Save the current game to the given file */
10577 SaveGameToFile(filename, append)
10584 if (strcmp(filename, "-") == 0) {
10585 return SaveGame(stdout, 0, NULL);
10587 f = fopen(filename, append ? "a" : "w");
10589 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10590 DisplayError(buf, errno);
10593 return SaveGame(f, 0, NULL);
10602 static char buf[MSG_SIZ];
10605 p = strchr(str, ' ');
10606 if (p == NULL) return str;
10607 strncpy(buf, str, p - str);
10608 buf[p - str] = NULLCHAR;
10612 #define PGN_MAX_LINE 75
10614 #define PGN_SIDE_WHITE 0
10615 #define PGN_SIDE_BLACK 1
10618 static int FindFirstMoveOutOfBook( int side )
10622 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10623 int index = backwardMostMove;
10624 int has_book_hit = 0;
10626 if( (index % 2) != side ) {
10630 while( index < forwardMostMove ) {
10631 /* Check to see if engine is in book */
10632 int depth = pvInfoList[index].depth;
10633 int score = pvInfoList[index].score;
10639 else if( score == 0 && depth == 63 ) {
10640 in_book = 1; /* Zappa */
10642 else if( score == 2 && depth == 99 ) {
10643 in_book = 1; /* Abrok */
10646 has_book_hit += in_book;
10662 void GetOutOfBookInfo( char * buf )
10666 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10668 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10669 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10673 if( oob[0] >= 0 || oob[1] >= 0 ) {
10674 for( i=0; i<2; i++ ) {
10678 if( i > 0 && oob[0] >= 0 ) {
10679 strcat( buf, " " );
10682 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10683 sprintf( buf+strlen(buf), "%s%.2f",
10684 pvInfoList[idx].score >= 0 ? "+" : "",
10685 pvInfoList[idx].score / 100.0 );
10691 /* Save game in PGN style and close the file */
10696 int i, offset, linelen, newblock;
10700 int movelen, numlen, blank;
10701 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10703 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10705 tm = time((time_t *) NULL);
10707 PrintPGNTags(f, &gameInfo);
10709 if (backwardMostMove > 0 || startedFromSetupPosition) {
10710 char *fen = PositionToFEN(backwardMostMove, NULL);
10711 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10712 fprintf(f, "\n{--------------\n");
10713 PrintPosition(f, backwardMostMove);
10714 fprintf(f, "--------------}\n");
10718 /* [AS] Out of book annotation */
10719 if( appData.saveOutOfBookInfo ) {
10722 GetOutOfBookInfo( buf );
10724 if( buf[0] != '\0' ) {
10725 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10732 i = backwardMostMove;
10736 while (i < forwardMostMove) {
10737 /* Print comments preceding this move */
10738 if (commentList[i] != NULL) {
10739 if (linelen > 0) fprintf(f, "\n");
10740 fprintf(f, "%s", commentList[i]);
10745 /* Format move number */
10746 if ((i % 2) == 0) {
10747 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10750 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10752 numtext[0] = NULLCHAR;
10755 numlen = strlen(numtext);
10758 /* Print move number */
10759 blank = linelen > 0 && numlen > 0;
10760 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10769 fprintf(f, "%s", numtext);
10773 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10774 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10777 blank = linelen > 0 && movelen > 0;
10778 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10787 fprintf(f, "%s", move_buffer);
10788 linelen += movelen;
10790 /* [AS] Add PV info if present */
10791 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10792 /* [HGM] add time */
10793 char buf[MSG_SIZ]; int seconds;
10795 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10797 if( seconds <= 0) buf[0] = 0; else
10798 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10799 seconds = (seconds + 4)/10; // round to full seconds
10800 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10801 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10804 sprintf( move_buffer, "{%s%.2f/%d%s}",
10805 pvInfoList[i].score >= 0 ? "+" : "",
10806 pvInfoList[i].score / 100.0,
10807 pvInfoList[i].depth,
10810 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10812 /* Print score/depth */
10813 blank = linelen > 0 && movelen > 0;
10814 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10823 fprintf(f, "%s", move_buffer);
10824 linelen += movelen;
10830 /* Start a new line */
10831 if (linelen > 0) fprintf(f, "\n");
10833 /* Print comments after last move */
10834 if (commentList[i] != NULL) {
10835 fprintf(f, "%s\n", commentList[i]);
10839 if (gameInfo.resultDetails != NULL &&
10840 gameInfo.resultDetails[0] != NULLCHAR) {
10841 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10842 PGNResult(gameInfo.result));
10844 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10848 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10852 /* Save game in old style and close the file */
10854 SaveGameOldStyle(f)
10860 tm = time((time_t *) NULL);
10862 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10865 if (backwardMostMove > 0 || startedFromSetupPosition) {
10866 fprintf(f, "\n[--------------\n");
10867 PrintPosition(f, backwardMostMove);
10868 fprintf(f, "--------------]\n");
10873 i = backwardMostMove;
10874 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10876 while (i < forwardMostMove) {
10877 if (commentList[i] != NULL) {
10878 fprintf(f, "[%s]\n", commentList[i]);
10881 if ((i % 2) == 1) {
10882 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10885 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10887 if (commentList[i] != NULL) {
10891 if (i >= forwardMostMove) {
10895 fprintf(f, "%s\n", parseList[i]);
10900 if (commentList[i] != NULL) {
10901 fprintf(f, "[%s]\n", commentList[i]);
10904 /* This isn't really the old style, but it's close enough */
10905 if (gameInfo.resultDetails != NULL &&
10906 gameInfo.resultDetails[0] != NULLCHAR) {
10907 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10908 gameInfo.resultDetails);
10910 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10917 /* Save the current game to open file f and close the file */
10919 SaveGame(f, dummy, dummy2)
10924 if (gameMode == EditPosition) EditPositionDone(TRUE);
10925 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10926 if (appData.oldSaveStyle)
10927 return SaveGameOldStyle(f);
10929 return SaveGamePGN(f);
10932 /* Save the current position to the given file */
10934 SavePositionToFile(filename)
10940 if (strcmp(filename, "-") == 0) {
10941 return SavePosition(stdout, 0, NULL);
10943 f = fopen(filename, "a");
10945 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10946 DisplayError(buf, errno);
10949 SavePosition(f, 0, NULL);
10955 /* Save the current position to the given open file and close the file */
10957 SavePosition(f, dummy, dummy2)
10965 if (gameMode == EditPosition) EditPositionDone(TRUE);
10966 if (appData.oldSaveStyle) {
10967 tm = time((time_t *) NULL);
10969 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10971 fprintf(f, "[--------------\n");
10972 PrintPosition(f, currentMove);
10973 fprintf(f, "--------------]\n");
10975 fen = PositionToFEN(currentMove, NULL);
10976 fprintf(f, "%s\n", fen);
10984 ReloadCmailMsgEvent(unregister)
10988 static char *inFilename = NULL;
10989 static char *outFilename;
10991 struct stat inbuf, outbuf;
10994 /* Any registered moves are unregistered if unregister is set, */
10995 /* i.e. invoked by the signal handler */
10997 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10998 cmailMoveRegistered[i] = FALSE;
10999 if (cmailCommentList[i] != NULL) {
11000 free(cmailCommentList[i]);
11001 cmailCommentList[i] = NULL;
11004 nCmailMovesRegistered = 0;
11007 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11008 cmailResult[i] = CMAIL_NOT_RESULT;
11012 if (inFilename == NULL) {
11013 /* Because the filenames are static they only get malloced once */
11014 /* and they never get freed */
11015 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11016 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11018 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11019 sprintf(outFilename, "%s.out", appData.cmailGameName);
11022 status = stat(outFilename, &outbuf);
11024 cmailMailedMove = FALSE;
11026 status = stat(inFilename, &inbuf);
11027 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11030 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11031 counts the games, notes how each one terminated, etc.
11033 It would be nice to remove this kludge and instead gather all
11034 the information while building the game list. (And to keep it
11035 in the game list nodes instead of having a bunch of fixed-size
11036 parallel arrays.) Note this will require getting each game's
11037 termination from the PGN tags, as the game list builder does
11038 not process the game moves. --mann
11040 cmailMsgLoaded = TRUE;
11041 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11043 /* Load first game in the file or popup game menu */
11044 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11046 #endif /* !WIN32 */
11054 char string[MSG_SIZ];
11056 if ( cmailMailedMove
11057 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11058 return TRUE; /* Allow free viewing */
11061 /* Unregister move to ensure that we don't leave RegisterMove */
11062 /* with the move registered when the conditions for registering no */
11064 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11065 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11066 nCmailMovesRegistered --;
11068 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11070 free(cmailCommentList[lastLoadGameNumber - 1]);
11071 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11075 if (cmailOldMove == -1) {
11076 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11080 if (currentMove > cmailOldMove + 1) {
11081 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11085 if (currentMove < cmailOldMove) {
11086 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11090 if (forwardMostMove > currentMove) {
11091 /* Silently truncate extra moves */
11095 if ( (currentMove == cmailOldMove + 1)
11096 || ( (currentMove == cmailOldMove)
11097 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11098 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11099 if (gameInfo.result != GameUnfinished) {
11100 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11103 if (commentList[currentMove] != NULL) {
11104 cmailCommentList[lastLoadGameNumber - 1]
11105 = StrSave(commentList[currentMove]);
11107 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11109 if (appData.debugMode)
11110 fprintf(debugFP, "Saving %s for game %d\n",
11111 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11114 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11116 f = fopen(string, "w");
11117 if (appData.oldSaveStyle) {
11118 SaveGameOldStyle(f); /* also closes the file */
11120 sprintf(string, "%s.pos.out", appData.cmailGameName);
11121 f = fopen(string, "w");
11122 SavePosition(f, 0, NULL); /* also closes the file */
11124 fprintf(f, "{--------------\n");
11125 PrintPosition(f, currentMove);
11126 fprintf(f, "--------------}\n\n");
11128 SaveGame(f, 0, NULL); /* also closes the file*/
11131 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11132 nCmailMovesRegistered ++;
11133 } else if (nCmailGames == 1) {
11134 DisplayError(_("You have not made a move yet"), 0);
11145 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11146 FILE *commandOutput;
11147 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11148 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11154 if (! cmailMsgLoaded) {
11155 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11159 if (nCmailGames == nCmailResults) {
11160 DisplayError(_("No unfinished games"), 0);
11164 #if CMAIL_PROHIBIT_REMAIL
11165 if (cmailMailedMove) {
11166 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);
11167 DisplayError(msg, 0);
11172 if (! (cmailMailedMove || RegisterMove())) return;
11174 if ( cmailMailedMove
11175 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11176 sprintf(string, partCommandString,
11177 appData.debugMode ? " -v" : "", appData.cmailGameName);
11178 commandOutput = popen(string, "r");
11180 if (commandOutput == NULL) {
11181 DisplayError(_("Failed to invoke cmail"), 0);
11183 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11184 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11186 if (nBuffers > 1) {
11187 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11188 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11189 nBytes = MSG_SIZ - 1;
11191 (void) memcpy(msg, buffer, nBytes);
11193 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11195 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11196 cmailMailedMove = TRUE; /* Prevent >1 moves */
11199 for (i = 0; i < nCmailGames; i ++) {
11200 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11205 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11207 sprintf(buffer, "%s/%s.%s.archive",
11209 appData.cmailGameName,
11211 LoadGameFromFile(buffer, 1, buffer, FALSE);
11212 cmailMsgLoaded = FALSE;
11216 DisplayInformation(msg);
11217 pclose(commandOutput);
11220 if ((*cmailMsg) != '\0') {
11221 DisplayInformation(cmailMsg);
11226 #endif /* !WIN32 */
11235 int prependComma = 0;
11237 char string[MSG_SIZ]; /* Space for game-list */
11240 if (!cmailMsgLoaded) return "";
11242 if (cmailMailedMove) {
11243 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11245 /* Create a list of games left */
11246 sprintf(string, "[");
11247 for (i = 0; i < nCmailGames; i ++) {
11248 if (! ( cmailMoveRegistered[i]
11249 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11250 if (prependComma) {
11251 sprintf(number, ",%d", i + 1);
11253 sprintf(number, "%d", i + 1);
11257 strcat(string, number);
11260 strcat(string, "]");
11262 if (nCmailMovesRegistered + nCmailResults == 0) {
11263 switch (nCmailGames) {
11266 _("Still need to make move for game\n"));
11271 _("Still need to make moves for both games\n"));
11276 _("Still need to make moves for all %d games\n"),
11281 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11284 _("Still need to make a move for game %s\n"),
11289 if (nCmailResults == nCmailGames) {
11290 sprintf(cmailMsg, _("No unfinished games\n"));
11292 sprintf(cmailMsg, _("Ready to send mail\n"));
11298 _("Still need to make moves for games %s\n"),
11310 if (gameMode == Training)
11311 SetTrainingModeOff();
11314 cmailMsgLoaded = FALSE;
11315 if (appData.icsActive) {
11316 SendToICS(ics_prefix);
11317 SendToICS("refresh\n");
11327 /* Give up on clean exit */
11331 /* Keep trying for clean exit */
11335 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11337 if (telnetISR != NULL) {
11338 RemoveInputSource(telnetISR);
11340 if (icsPR != NoProc) {
11341 DestroyChildProcess(icsPR, TRUE);
11344 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11345 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11347 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11348 /* make sure this other one finishes before killing it! */
11349 if(endingGame) { int count = 0;
11350 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11351 while(endingGame && count++ < 10) DoSleep(1);
11352 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11355 /* Kill off chess programs */
11356 if (first.pr != NoProc) {
11359 DoSleep( appData.delayBeforeQuit );
11360 SendToProgram("quit\n", &first);
11361 DoSleep( appData.delayAfterQuit );
11362 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11364 if (second.pr != NoProc) {
11365 DoSleep( appData.delayBeforeQuit );
11366 SendToProgram("quit\n", &second);
11367 DoSleep( appData.delayAfterQuit );
11368 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11370 if (first.isr != NULL) {
11371 RemoveInputSource(first.isr);
11373 if (second.isr != NULL) {
11374 RemoveInputSource(second.isr);
11377 ShutDownFrontEnd();
11384 if (appData.debugMode)
11385 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11389 if (gameMode == MachinePlaysWhite ||
11390 gameMode == MachinePlaysBlack) {
11393 DisplayBothClocks();
11395 if (gameMode == PlayFromGameFile) {
11396 if (appData.timeDelay >= 0)
11397 AutoPlayGameLoop();
11398 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11399 Reset(FALSE, TRUE);
11400 SendToICS(ics_prefix);
11401 SendToICS("refresh\n");
11402 } else if (currentMove < forwardMostMove) {
11403 ForwardInner(forwardMostMove);
11405 pauseExamInvalid = FALSE;
11407 switch (gameMode) {
11411 pauseExamForwardMostMove = forwardMostMove;
11412 pauseExamInvalid = FALSE;
11415 case IcsPlayingWhite:
11416 case IcsPlayingBlack:
11420 case PlayFromGameFile:
11421 (void) StopLoadGameTimer();
11425 case BeginningOfGame:
11426 if (appData.icsActive) return;
11427 /* else fall through */
11428 case MachinePlaysWhite:
11429 case MachinePlaysBlack:
11430 case TwoMachinesPlay:
11431 if (forwardMostMove == 0)
11432 return; /* don't pause if no one has moved */
11433 if ((gameMode == MachinePlaysWhite &&
11434 !WhiteOnMove(forwardMostMove)) ||
11435 (gameMode == MachinePlaysBlack &&
11436 WhiteOnMove(forwardMostMove))) {
11449 char title[MSG_SIZ];
11451 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11452 strcpy(title, _("Edit comment"));
11454 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11455 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11456 parseList[currentMove - 1]);
11459 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11466 char *tags = PGNTags(&gameInfo);
11467 EditTagsPopUp(tags);
11474 if (appData.noChessProgram || gameMode == AnalyzeMode)
11477 if (gameMode != AnalyzeFile) {
11478 if (!appData.icsEngineAnalyze) {
11480 if (gameMode != EditGame) return;
11482 ResurrectChessProgram();
11483 SendToProgram("analyze\n", &first);
11484 first.analyzing = TRUE;
11485 /*first.maybeThinking = TRUE;*/
11486 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11487 EngineOutputPopUp();
11489 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11494 StartAnalysisClock();
11495 GetTimeMark(&lastNodeCountTime);
11502 if (appData.noChessProgram || gameMode == AnalyzeFile)
11505 if (gameMode != AnalyzeMode) {
11507 if (gameMode != EditGame) return;
11508 ResurrectChessProgram();
11509 SendToProgram("analyze\n", &first);
11510 first.analyzing = TRUE;
11511 /*first.maybeThinking = TRUE;*/
11512 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11513 EngineOutputPopUp();
11515 gameMode = AnalyzeFile;
11520 StartAnalysisClock();
11521 GetTimeMark(&lastNodeCountTime);
11526 MachineWhiteEvent()
11529 char *bookHit = NULL;
11531 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11535 if (gameMode == PlayFromGameFile ||
11536 gameMode == TwoMachinesPlay ||
11537 gameMode == Training ||
11538 gameMode == AnalyzeMode ||
11539 gameMode == EndOfGame)
11542 if (gameMode == EditPosition)
11543 EditPositionDone(TRUE);
11545 if (!WhiteOnMove(currentMove)) {
11546 DisplayError(_("It is not White's turn"), 0);
11550 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11553 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11554 gameMode == AnalyzeFile)
11557 ResurrectChessProgram(); /* in case it isn't running */
11558 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11559 gameMode = MachinePlaysWhite;
11562 gameMode = MachinePlaysWhite;
11566 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11568 if (first.sendName) {
11569 sprintf(buf, "name %s\n", gameInfo.black);
11570 SendToProgram(buf, &first);
11572 if (first.sendTime) {
11573 if (first.useColors) {
11574 SendToProgram("black\n", &first); /*gnu kludge*/
11576 SendTimeRemaining(&first, TRUE);
11578 if (first.useColors) {
11579 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11581 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11582 SetMachineThinkingEnables();
11583 first.maybeThinking = TRUE;
11587 if (appData.autoFlipView && !flipView) {
11588 flipView = !flipView;
11589 DrawPosition(FALSE, NULL);
11590 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11593 if(bookHit) { // [HGM] book: simulate book reply
11594 static char bookMove[MSG_SIZ]; // a bit generous?
11596 programStats.nodes = programStats.depth = programStats.time =
11597 programStats.score = programStats.got_only_move = 0;
11598 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11600 strcpy(bookMove, "move ");
11601 strcat(bookMove, bookHit);
11602 HandleMachineMove(bookMove, &first);
11607 MachineBlackEvent()
11610 char *bookHit = NULL;
11612 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11616 if (gameMode == PlayFromGameFile ||
11617 gameMode == TwoMachinesPlay ||
11618 gameMode == Training ||
11619 gameMode == AnalyzeMode ||
11620 gameMode == EndOfGame)
11623 if (gameMode == EditPosition)
11624 EditPositionDone(TRUE);
11626 if (WhiteOnMove(currentMove)) {
11627 DisplayError(_("It is not Black's turn"), 0);
11631 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11634 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11635 gameMode == AnalyzeFile)
11638 ResurrectChessProgram(); /* in case it isn't running */
11639 gameMode = MachinePlaysBlack;
11643 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11645 if (first.sendName) {
11646 sprintf(buf, "name %s\n", gameInfo.white);
11647 SendToProgram(buf, &first);
11649 if (first.sendTime) {
11650 if (first.useColors) {
11651 SendToProgram("white\n", &first); /*gnu kludge*/
11653 SendTimeRemaining(&first, FALSE);
11655 if (first.useColors) {
11656 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11658 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11659 SetMachineThinkingEnables();
11660 first.maybeThinking = TRUE;
11663 if (appData.autoFlipView && flipView) {
11664 flipView = !flipView;
11665 DrawPosition(FALSE, NULL);
11666 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11668 if(bookHit) { // [HGM] book: simulate book reply
11669 static char bookMove[MSG_SIZ]; // a bit generous?
11671 programStats.nodes = programStats.depth = programStats.time =
11672 programStats.score = programStats.got_only_move = 0;
11673 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11675 strcpy(bookMove, "move ");
11676 strcat(bookMove, bookHit);
11677 HandleMachineMove(bookMove, &first);
11683 DisplayTwoMachinesTitle()
11686 if (appData.matchGames > 0) {
11687 if (first.twoMachinesColor[0] == 'w') {
11688 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11689 gameInfo.white, gameInfo.black,
11690 first.matchWins, second.matchWins,
11691 matchGame - 1 - (first.matchWins + second.matchWins));
11693 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11694 gameInfo.white, gameInfo.black,
11695 second.matchWins, first.matchWins,
11696 matchGame - 1 - (first.matchWins + second.matchWins));
11699 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11705 TwoMachinesEvent P((void))
11709 ChessProgramState *onmove;
11710 char *bookHit = NULL;
11712 if (appData.noChessProgram) return;
11714 switch (gameMode) {
11715 case TwoMachinesPlay:
11717 case MachinePlaysWhite:
11718 case MachinePlaysBlack:
11719 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11720 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11724 case BeginningOfGame:
11725 case PlayFromGameFile:
11728 if (gameMode != EditGame) return;
11731 EditPositionDone(TRUE);
11742 // forwardMostMove = currentMove;
11743 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11744 ResurrectChessProgram(); /* in case first program isn't running */
11746 if (second.pr == NULL) {
11747 StartChessProgram(&second);
11748 if (second.protocolVersion == 1) {
11749 TwoMachinesEventIfReady();
11751 /* kludge: allow timeout for initial "feature" command */
11753 DisplayMessage("", _("Starting second chess program"));
11754 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11758 DisplayMessage("", "");
11759 InitChessProgram(&second, FALSE);
11760 SendToProgram("force\n", &second);
11761 if (startedFromSetupPosition) {
11762 SendBoard(&second, backwardMostMove);
11763 if (appData.debugMode) {
11764 fprintf(debugFP, "Two Machines\n");
11767 for (i = backwardMostMove; i < forwardMostMove; i++) {
11768 SendMoveToProgram(i, &second);
11771 gameMode = TwoMachinesPlay;
11775 DisplayTwoMachinesTitle();
11777 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11783 SendToProgram(first.computerString, &first);
11784 if (first.sendName) {
11785 sprintf(buf, "name %s\n", second.tidy);
11786 SendToProgram(buf, &first);
11788 SendToProgram(second.computerString, &second);
11789 if (second.sendName) {
11790 sprintf(buf, "name %s\n", first.tidy);
11791 SendToProgram(buf, &second);
11795 if (!first.sendTime || !second.sendTime) {
11796 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11797 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11799 if (onmove->sendTime) {
11800 if (onmove->useColors) {
11801 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11803 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11805 if (onmove->useColors) {
11806 SendToProgram(onmove->twoMachinesColor, onmove);
11808 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11809 // SendToProgram("go\n", onmove);
11810 onmove->maybeThinking = TRUE;
11811 SetMachineThinkingEnables();
11815 if(bookHit) { // [HGM] book: simulate book reply
11816 static char bookMove[MSG_SIZ]; // a bit generous?
11818 programStats.nodes = programStats.depth = programStats.time =
11819 programStats.score = programStats.got_only_move = 0;
11820 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11822 strcpy(bookMove, "move ");
11823 strcat(bookMove, bookHit);
11824 savedMessage = bookMove; // args for deferred call
11825 savedState = onmove;
11826 ScheduleDelayedEvent(DeferredBookMove, 1);
11833 if (gameMode == Training) {
11834 SetTrainingModeOff();
11835 gameMode = PlayFromGameFile;
11836 DisplayMessage("", _("Training mode off"));
11838 gameMode = Training;
11839 animateTraining = appData.animate;
11841 /* make sure we are not already at the end of the game */
11842 if (currentMove < forwardMostMove) {
11843 SetTrainingModeOn();
11844 DisplayMessage("", _("Training mode on"));
11846 gameMode = PlayFromGameFile;
11847 DisplayError(_("Already at end of game"), 0);
11856 if (!appData.icsActive) return;
11857 switch (gameMode) {
11858 case IcsPlayingWhite:
11859 case IcsPlayingBlack:
11862 case BeginningOfGame:
11870 EditPositionDone(TRUE);
11883 gameMode = IcsIdle;
11894 switch (gameMode) {
11896 SetTrainingModeOff();
11898 case MachinePlaysWhite:
11899 case MachinePlaysBlack:
11900 case BeginningOfGame:
11901 SendToProgram("force\n", &first);
11902 SetUserThinkingEnables();
11904 case PlayFromGameFile:
11905 (void) StopLoadGameTimer();
11906 if (gameFileFP != NULL) {
11911 EditPositionDone(TRUE);
11916 SendToProgram("force\n", &first);
11918 case TwoMachinesPlay:
11919 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11920 ResurrectChessProgram();
11921 SetUserThinkingEnables();
11924 ResurrectChessProgram();
11926 case IcsPlayingBlack:
11927 case IcsPlayingWhite:
11928 DisplayError(_("Warning: You are still playing a game"), 0);
11931 DisplayError(_("Warning: You are still observing a game"), 0);
11934 DisplayError(_("Warning: You are still examining a game"), 0);
11945 first.offeredDraw = second.offeredDraw = 0;
11947 if (gameMode == PlayFromGameFile) {
11948 whiteTimeRemaining = timeRemaining[0][currentMove];
11949 blackTimeRemaining = timeRemaining[1][currentMove];
11953 if (gameMode == MachinePlaysWhite ||
11954 gameMode == MachinePlaysBlack ||
11955 gameMode == TwoMachinesPlay ||
11956 gameMode == EndOfGame) {
11957 i = forwardMostMove;
11958 while (i > currentMove) {
11959 SendToProgram("undo\n", &first);
11962 whiteTimeRemaining = timeRemaining[0][currentMove];
11963 blackTimeRemaining = timeRemaining[1][currentMove];
11964 DisplayBothClocks();
11965 if (whiteFlag || blackFlag) {
11966 whiteFlag = blackFlag = 0;
11971 gameMode = EditGame;
11978 EditPositionEvent()
11980 if (gameMode == EditPosition) {
11986 if (gameMode != EditGame) return;
11988 gameMode = EditPosition;
11991 if (currentMove > 0)
11992 CopyBoard(boards[0], boards[currentMove]);
11994 blackPlaysFirst = !WhiteOnMove(currentMove);
11996 currentMove = forwardMostMove = backwardMostMove = 0;
11997 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12004 /* [DM] icsEngineAnalyze - possible call from other functions */
12005 if (appData.icsEngineAnalyze) {
12006 appData.icsEngineAnalyze = FALSE;
12008 DisplayMessage("",_("Close ICS engine analyze..."));
12010 if (first.analysisSupport && first.analyzing) {
12011 SendToProgram("exit\n", &first);
12012 first.analyzing = FALSE;
12014 thinkOutput[0] = NULLCHAR;
12018 EditPositionDone(Boolean fakeRights)
12020 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12022 startedFromSetupPosition = TRUE;
12023 InitChessProgram(&first, FALSE);
12024 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12025 boards[0][EP_STATUS] = EP_NONE;
12026 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12027 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12028 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12029 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12030 } else boards[0][CASTLING][2] = NoRights;
12031 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12032 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12033 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12034 } else boards[0][CASTLING][5] = NoRights;
12036 SendToProgram("force\n", &first);
12037 if (blackPlaysFirst) {
12038 strcpy(moveList[0], "");
12039 strcpy(parseList[0], "");
12040 currentMove = forwardMostMove = backwardMostMove = 1;
12041 CopyBoard(boards[1], boards[0]);
12043 currentMove = forwardMostMove = backwardMostMove = 0;
12045 SendBoard(&first, forwardMostMove);
12046 if (appData.debugMode) {
12047 fprintf(debugFP, "EditPosDone\n");
12050 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12051 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12052 gameMode = EditGame;
12054 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12055 ClearHighlights(); /* [AS] */
12058 /* Pause for `ms' milliseconds */
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12069 } while (SubtractTimeMarks(&m2, &m1) < ms);
12072 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12074 SendMultiLineToICS(buf)
12077 char temp[MSG_SIZ+1], *p;
12084 strncpy(temp, buf, len);
12089 if (*p == '\n' || *p == '\r')
12094 strcat(temp, "\n");
12096 SendToPlayer(temp, strlen(temp));
12100 SetWhiteToPlayEvent()
12102 if (gameMode == EditPosition) {
12103 blackPlaysFirst = FALSE;
12104 DisplayBothClocks(); /* works because currentMove is 0 */
12105 } else if (gameMode == IcsExamining) {
12106 SendToICS(ics_prefix);
12107 SendToICS("tomove white\n");
12112 SetBlackToPlayEvent()
12114 if (gameMode == EditPosition) {
12115 blackPlaysFirst = TRUE;
12116 currentMove = 1; /* kludge */
12117 DisplayBothClocks();
12119 } else if (gameMode == IcsExamining) {
12120 SendToICS(ics_prefix);
12121 SendToICS("tomove black\n");
12126 EditPositionMenuEvent(selection, x, y)
12127 ChessSquare selection;
12131 ChessSquare piece = boards[0][y][x];
12133 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12135 switch (selection) {
12137 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12138 SendToICS(ics_prefix);
12139 SendToICS("bsetup clear\n");
12140 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12141 SendToICS(ics_prefix);
12142 SendToICS("clearboard\n");
12144 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12145 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12146 for (y = 0; y < BOARD_HEIGHT; y++) {
12147 if (gameMode == IcsExamining) {
12148 if (boards[currentMove][y][x] != EmptySquare) {
12149 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12154 boards[0][y][x] = p;
12159 if (gameMode == EditPosition) {
12160 DrawPosition(FALSE, boards[0]);
12165 SetWhiteToPlayEvent();
12169 SetBlackToPlayEvent();
12173 if (gameMode == IcsExamining) {
12174 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12175 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12178 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12179 if(x == BOARD_LEFT-2) {
12180 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12181 boards[0][y][1] = 0;
12183 if(x == BOARD_RGHT+1) {
12184 if(y >= gameInfo.holdingsSize) break;
12185 boards[0][y][BOARD_WIDTH-2] = 0;
12188 boards[0][y][x] = EmptySquare;
12189 DrawPosition(FALSE, boards[0]);
12194 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12195 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12196 selection = (ChessSquare) (PROMOTED piece);
12197 } else if(piece == EmptySquare) selection = WhiteSilver;
12198 else selection = (ChessSquare)((int)piece - 1);
12202 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12203 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12204 selection = (ChessSquare) (DEMOTED piece);
12205 } else if(piece == EmptySquare) selection = BlackSilver;
12206 else selection = (ChessSquare)((int)piece + 1);
12211 if(gameInfo.variant == VariantShatranj ||
12212 gameInfo.variant == VariantXiangqi ||
12213 gameInfo.variant == VariantCourier ||
12214 gameInfo.variant == VariantMakruk )
12215 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12220 if(gameInfo.variant == VariantXiangqi)
12221 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12222 if(gameInfo.variant == VariantKnightmate)
12223 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12226 if (gameMode == IcsExamining) {
12227 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12228 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12229 PieceToChar(selection), AAA + x, ONE + y);
12232 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12234 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12235 n = PieceToNumber(selection - BlackPawn);
12236 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12237 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12238 boards[0][BOARD_HEIGHT-1-n][1]++;
12240 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12241 n = PieceToNumber(selection);
12242 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12243 boards[0][n][BOARD_WIDTH-1] = selection;
12244 boards[0][n][BOARD_WIDTH-2]++;
12247 boards[0][y][x] = selection;
12248 DrawPosition(TRUE, boards[0]);
12256 DropMenuEvent(selection, x, y)
12257 ChessSquare selection;
12260 ChessMove moveType;
12262 switch (gameMode) {
12263 case IcsPlayingWhite:
12264 case MachinePlaysBlack:
12265 if (!WhiteOnMove(currentMove)) {
12266 DisplayMoveError(_("It is Black's turn"));
12269 moveType = WhiteDrop;
12271 case IcsPlayingBlack:
12272 case MachinePlaysWhite:
12273 if (WhiteOnMove(currentMove)) {
12274 DisplayMoveError(_("It is White's turn"));
12277 moveType = BlackDrop;
12280 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12286 if (moveType == BlackDrop && selection < BlackPawn) {
12287 selection = (ChessSquare) ((int) selection
12288 + (int) BlackPawn - (int) WhitePawn);
12290 if (boards[currentMove][y][x] != EmptySquare) {
12291 DisplayMoveError(_("That square is occupied"));
12295 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12301 /* Accept a pending offer of any kind from opponent */
12303 if (appData.icsActive) {
12304 SendToICS(ics_prefix);
12305 SendToICS("accept\n");
12306 } else if (cmailMsgLoaded) {
12307 if (currentMove == cmailOldMove &&
12308 commentList[cmailOldMove] != NULL &&
12309 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12310 "Black offers a draw" : "White offers a draw")) {
12312 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12313 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12315 DisplayError(_("There is no pending offer on this move"), 0);
12316 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12319 /* Not used for offers from chess program */
12326 /* Decline a pending offer of any kind from opponent */
12328 if (appData.icsActive) {
12329 SendToICS(ics_prefix);
12330 SendToICS("decline\n");
12331 } else if (cmailMsgLoaded) {
12332 if (currentMove == cmailOldMove &&
12333 commentList[cmailOldMove] != NULL &&
12334 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12335 "Black offers a draw" : "White offers a draw")) {
12337 AppendComment(cmailOldMove, "Draw declined", TRUE);
12338 DisplayComment(cmailOldMove - 1, "Draw declined");
12341 DisplayError(_("There is no pending offer on this move"), 0);
12344 /* Not used for offers from chess program */
12351 /* Issue ICS rematch command */
12352 if (appData.icsActive) {
12353 SendToICS(ics_prefix);
12354 SendToICS("rematch\n");
12361 /* Call your opponent's flag (claim a win on time) */
12362 if (appData.icsActive) {
12363 SendToICS(ics_prefix);
12364 SendToICS("flag\n");
12366 switch (gameMode) {
12369 case MachinePlaysWhite:
12372 GameEnds(GameIsDrawn, "Both players ran out of time",
12375 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12377 DisplayError(_("Your opponent is not out of time"), 0);
12380 case MachinePlaysBlack:
12383 GameEnds(GameIsDrawn, "Both players ran out of time",
12386 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12388 DisplayError(_("Your opponent is not out of time"), 0);
12398 /* Offer draw or accept pending draw offer from opponent */
12400 if (appData.icsActive) {
12401 /* Note: tournament rules require draw offers to be
12402 made after you make your move but before you punch
12403 your clock. Currently ICS doesn't let you do that;
12404 instead, you immediately punch your clock after making
12405 a move, but you can offer a draw at any time. */
12407 SendToICS(ics_prefix);
12408 SendToICS("draw\n");
12409 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12410 } else if (cmailMsgLoaded) {
12411 if (currentMove == cmailOldMove &&
12412 commentList[cmailOldMove] != NULL &&
12413 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12414 "Black offers a draw" : "White offers a draw")) {
12415 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12416 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12417 } else if (currentMove == cmailOldMove + 1) {
12418 char *offer = WhiteOnMove(cmailOldMove) ?
12419 "White offers a draw" : "Black offers a draw";
12420 AppendComment(currentMove, offer, TRUE);
12421 DisplayComment(currentMove - 1, offer);
12422 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12424 DisplayError(_("You must make your move before offering a draw"), 0);
12425 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12427 } else if (first.offeredDraw) {
12428 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12430 if (first.sendDrawOffers) {
12431 SendToProgram("draw\n", &first);
12432 userOfferedDraw = TRUE;
12440 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12442 if (appData.icsActive) {
12443 SendToICS(ics_prefix);
12444 SendToICS("adjourn\n");
12446 /* Currently GNU Chess doesn't offer or accept Adjourns */
12454 /* Offer Abort or accept pending Abort offer from opponent */
12456 if (appData.icsActive) {
12457 SendToICS(ics_prefix);
12458 SendToICS("abort\n");
12460 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12467 /* Resign. You can do this even if it's not your turn. */
12469 if (appData.icsActive) {
12470 SendToICS(ics_prefix);
12471 SendToICS("resign\n");
12473 switch (gameMode) {
12474 case MachinePlaysWhite:
12475 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12477 case MachinePlaysBlack:
12478 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12481 if (cmailMsgLoaded) {
12483 if (WhiteOnMove(cmailOldMove)) {
12484 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12486 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12488 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12499 StopObservingEvent()
12501 /* Stop observing current games */
12502 SendToICS(ics_prefix);
12503 SendToICS("unobserve\n");
12507 StopExaminingEvent()
12509 /* Stop observing current game */
12510 SendToICS(ics_prefix);
12511 SendToICS("unexamine\n");
12515 ForwardInner(target)
12520 if (appData.debugMode)
12521 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12522 target, currentMove, forwardMostMove);
12524 if (gameMode == EditPosition)
12527 if (gameMode == PlayFromGameFile && !pausing)
12530 if (gameMode == IcsExamining && pausing)
12531 limit = pauseExamForwardMostMove;
12533 limit = forwardMostMove;
12535 if (target > limit) target = limit;
12537 if (target > 0 && moveList[target - 1][0]) {
12538 int fromX, fromY, toX, toY;
12539 toX = moveList[target - 1][2] - AAA;
12540 toY = moveList[target - 1][3] - ONE;
12541 if (moveList[target - 1][1] == '@') {
12542 if (appData.highlightLastMove) {
12543 SetHighlights(-1, -1, toX, toY);
12546 fromX = moveList[target - 1][0] - AAA;
12547 fromY = moveList[target - 1][1] - ONE;
12548 if (target == currentMove + 1) {
12549 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12551 if (appData.highlightLastMove) {
12552 SetHighlights(fromX, fromY, toX, toY);
12556 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12557 gameMode == Training || gameMode == PlayFromGameFile ||
12558 gameMode == AnalyzeFile) {
12559 while (currentMove < target) {
12560 SendMoveToProgram(currentMove++, &first);
12563 currentMove = target;
12566 if (gameMode == EditGame || gameMode == EndOfGame) {
12567 whiteTimeRemaining = timeRemaining[0][currentMove];
12568 blackTimeRemaining = timeRemaining[1][currentMove];
12570 DisplayBothClocks();
12571 DisplayMove(currentMove - 1);
12572 DrawPosition(FALSE, boards[currentMove]);
12573 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12574 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12575 DisplayComment(currentMove - 1, commentList[currentMove]);
12583 if (gameMode == IcsExamining && !pausing) {
12584 SendToICS(ics_prefix);
12585 SendToICS("forward\n");
12587 ForwardInner(currentMove + 1);
12594 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12595 /* to optimze, we temporarily turn off analysis mode while we feed
12596 * the remaining moves to the engine. Otherwise we get analysis output
12599 if (first.analysisSupport) {
12600 SendToProgram("exit\nforce\n", &first);
12601 first.analyzing = FALSE;
12605 if (gameMode == IcsExamining && !pausing) {
12606 SendToICS(ics_prefix);
12607 SendToICS("forward 999999\n");
12609 ForwardInner(forwardMostMove);
12612 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12613 /* we have fed all the moves, so reactivate analysis mode */
12614 SendToProgram("analyze\n", &first);
12615 first.analyzing = TRUE;
12616 /*first.maybeThinking = TRUE;*/
12617 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12622 BackwardInner(target)
12625 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12627 if (appData.debugMode)
12628 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12629 target, currentMove, forwardMostMove);
12631 if (gameMode == EditPosition) return;
12632 if (currentMove <= backwardMostMove) {
12634 DrawPosition(full_redraw, boards[currentMove]);
12637 if (gameMode == PlayFromGameFile && !pausing)
12640 if (moveList[target][0]) {
12641 int fromX, fromY, toX, toY;
12642 toX = moveList[target][2] - AAA;
12643 toY = moveList[target][3] - ONE;
12644 if (moveList[target][1] == '@') {
12645 if (appData.highlightLastMove) {
12646 SetHighlights(-1, -1, toX, toY);
12649 fromX = moveList[target][0] - AAA;
12650 fromY = moveList[target][1] - ONE;
12651 if (target == currentMove - 1) {
12652 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12654 if (appData.highlightLastMove) {
12655 SetHighlights(fromX, fromY, toX, toY);
12659 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12660 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12661 while (currentMove > target) {
12662 SendToProgram("undo\n", &first);
12666 currentMove = target;
12669 if (gameMode == EditGame || gameMode == EndOfGame) {
12670 whiteTimeRemaining = timeRemaining[0][currentMove];
12671 blackTimeRemaining = timeRemaining[1][currentMove];
12673 DisplayBothClocks();
12674 DisplayMove(currentMove - 1);
12675 DrawPosition(full_redraw, boards[currentMove]);
12676 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12677 // [HGM] PV info: routine tests if comment empty
12678 DisplayComment(currentMove - 1, commentList[currentMove]);
12684 if (gameMode == IcsExamining && !pausing) {
12685 SendToICS(ics_prefix);
12686 SendToICS("backward\n");
12688 BackwardInner(currentMove - 1);
12695 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12696 /* to optimize, we temporarily turn off analysis mode while we undo
12697 * all the moves. Otherwise we get analysis output after each undo.
12699 if (first.analysisSupport) {
12700 SendToProgram("exit\nforce\n", &first);
12701 first.analyzing = FALSE;
12705 if (gameMode == IcsExamining && !pausing) {
12706 SendToICS(ics_prefix);
12707 SendToICS("backward 999999\n");
12709 BackwardInner(backwardMostMove);
12712 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12713 /* we have fed all the moves, so reactivate analysis mode */
12714 SendToProgram("analyze\n", &first);
12715 first.analyzing = TRUE;
12716 /*first.maybeThinking = TRUE;*/
12717 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12724 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12725 if (to >= forwardMostMove) to = forwardMostMove;
12726 if (to <= backwardMostMove) to = backwardMostMove;
12727 if (to < currentMove) {
12735 RevertEvent(Boolean annotate)
12737 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12740 if (gameMode != IcsExamining) {
12741 DisplayError(_("You are not examining a game"), 0);
12745 DisplayError(_("You can't revert while pausing"), 0);
12748 SendToICS(ics_prefix);
12749 SendToICS("revert\n");
12755 switch (gameMode) {
12756 case MachinePlaysWhite:
12757 case MachinePlaysBlack:
12758 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12759 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12762 if (forwardMostMove < 2) return;
12763 currentMove = forwardMostMove = forwardMostMove - 2;
12764 whiteTimeRemaining = timeRemaining[0][currentMove];
12765 blackTimeRemaining = timeRemaining[1][currentMove];
12766 DisplayBothClocks();
12767 DisplayMove(currentMove - 1);
12768 ClearHighlights();/*!! could figure this out*/
12769 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12770 SendToProgram("remove\n", &first);
12771 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12774 case BeginningOfGame:
12778 case IcsPlayingWhite:
12779 case IcsPlayingBlack:
12780 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12781 SendToICS(ics_prefix);
12782 SendToICS("takeback 2\n");
12784 SendToICS(ics_prefix);
12785 SendToICS("takeback 1\n");
12794 ChessProgramState *cps;
12796 switch (gameMode) {
12797 case MachinePlaysWhite:
12798 if (!WhiteOnMove(forwardMostMove)) {
12799 DisplayError(_("It is your turn"), 0);
12804 case MachinePlaysBlack:
12805 if (WhiteOnMove(forwardMostMove)) {
12806 DisplayError(_("It is your turn"), 0);
12811 case TwoMachinesPlay:
12812 if (WhiteOnMove(forwardMostMove) ==
12813 (first.twoMachinesColor[0] == 'w')) {
12819 case BeginningOfGame:
12823 SendToProgram("?\n", cps);
12827 TruncateGameEvent()
12830 if (gameMode != EditGame) return;
12837 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12838 if (forwardMostMove > currentMove) {
12839 if (gameInfo.resultDetails != NULL) {
12840 free(gameInfo.resultDetails);
12841 gameInfo.resultDetails = NULL;
12842 gameInfo.result = GameUnfinished;
12844 forwardMostMove = currentMove;
12845 HistorySet(parseList, backwardMostMove, forwardMostMove,
12853 if (appData.noChessProgram) return;
12854 switch (gameMode) {
12855 case MachinePlaysWhite:
12856 if (WhiteOnMove(forwardMostMove)) {
12857 DisplayError(_("Wait until your turn"), 0);
12861 case BeginningOfGame:
12862 case MachinePlaysBlack:
12863 if (!WhiteOnMove(forwardMostMove)) {
12864 DisplayError(_("Wait until your turn"), 0);
12869 DisplayError(_("No hint available"), 0);
12872 SendToProgram("hint\n", &first);
12873 hintRequested = TRUE;
12879 if (appData.noChessProgram) return;
12880 switch (gameMode) {
12881 case MachinePlaysWhite:
12882 if (WhiteOnMove(forwardMostMove)) {
12883 DisplayError(_("Wait until your turn"), 0);
12887 case BeginningOfGame:
12888 case MachinePlaysBlack:
12889 if (!WhiteOnMove(forwardMostMove)) {
12890 DisplayError(_("Wait until your turn"), 0);
12895 EditPositionDone(TRUE);
12897 case TwoMachinesPlay:
12902 SendToProgram("bk\n", &first);
12903 bookOutput[0] = NULLCHAR;
12904 bookRequested = TRUE;
12910 char *tags = PGNTags(&gameInfo);
12911 TagsPopUp(tags, CmailMsg());
12915 /* end button procedures */
12918 PrintPosition(fp, move)
12924 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12925 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12926 char c = PieceToChar(boards[move][i][j]);
12927 fputc(c == 'x' ? '.' : c, fp);
12928 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12931 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12932 fprintf(fp, "white to play\n");
12934 fprintf(fp, "black to play\n");
12941 if (gameInfo.white != NULL) {
12942 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12948 /* Find last component of program's own name, using some heuristics */
12950 TidyProgramName(prog, host, buf)
12951 char *prog, *host, buf[MSG_SIZ];
12954 int local = (strcmp(host, "localhost") == 0);
12955 while (!local && (p = strchr(prog, ';')) != NULL) {
12957 while (*p == ' ') p++;
12960 if (*prog == '"' || *prog == '\'') {
12961 q = strchr(prog + 1, *prog);
12963 q = strchr(prog, ' ');
12965 if (q == NULL) q = prog + strlen(prog);
12967 while (p >= prog && *p != '/' && *p != '\\') p--;
12969 if(p == prog && *p == '"') p++;
12970 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12971 memcpy(buf, p, q - p);
12972 buf[q - p] = NULLCHAR;
12980 TimeControlTagValue()
12983 if (!appData.clockMode) {
12985 } else if (movesPerSession > 0) {
12986 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12987 } else if (timeIncrement == 0) {
12988 sprintf(buf, "%ld", timeControl/1000);
12990 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12992 return StrSave(buf);
12998 /* This routine is used only for certain modes */
12999 VariantClass v = gameInfo.variant;
13000 ChessMove r = GameUnfinished;
13003 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13004 r = gameInfo.result;
13005 p = gameInfo.resultDetails;
13006 gameInfo.resultDetails = NULL;
13008 ClearGameInfo(&gameInfo);
13009 gameInfo.variant = v;
13011 switch (gameMode) {
13012 case MachinePlaysWhite:
13013 gameInfo.event = StrSave( appData.pgnEventHeader );
13014 gameInfo.site = StrSave(HostName());
13015 gameInfo.date = PGNDate();
13016 gameInfo.round = StrSave("-");
13017 gameInfo.white = StrSave(first.tidy);
13018 gameInfo.black = StrSave(UserName());
13019 gameInfo.timeControl = TimeControlTagValue();
13022 case MachinePlaysBlack:
13023 gameInfo.event = StrSave( appData.pgnEventHeader );
13024 gameInfo.site = StrSave(HostName());
13025 gameInfo.date = PGNDate();
13026 gameInfo.round = StrSave("-");
13027 gameInfo.white = StrSave(UserName());
13028 gameInfo.black = StrSave(first.tidy);
13029 gameInfo.timeControl = TimeControlTagValue();
13032 case TwoMachinesPlay:
13033 gameInfo.event = StrSave( appData.pgnEventHeader );
13034 gameInfo.site = StrSave(HostName());
13035 gameInfo.date = PGNDate();
13036 if (matchGame > 0) {
13038 sprintf(buf, "%d", matchGame);
13039 gameInfo.round = StrSave(buf);
13041 gameInfo.round = StrSave("-");
13043 if (first.twoMachinesColor[0] == 'w') {
13044 gameInfo.white = StrSave(first.tidy);
13045 gameInfo.black = StrSave(second.tidy);
13047 gameInfo.white = StrSave(second.tidy);
13048 gameInfo.black = StrSave(first.tidy);
13050 gameInfo.timeControl = TimeControlTagValue();
13054 gameInfo.event = StrSave("Edited game");
13055 gameInfo.site = StrSave(HostName());
13056 gameInfo.date = PGNDate();
13057 gameInfo.round = StrSave("-");
13058 gameInfo.white = StrSave("-");
13059 gameInfo.black = StrSave("-");
13060 gameInfo.result = r;
13061 gameInfo.resultDetails = p;
13065 gameInfo.event = StrSave("Edited position");
13066 gameInfo.site = StrSave(HostName());
13067 gameInfo.date = PGNDate();
13068 gameInfo.round = StrSave("-");
13069 gameInfo.white = StrSave("-");
13070 gameInfo.black = StrSave("-");
13073 case IcsPlayingWhite:
13074 case IcsPlayingBlack:
13079 case PlayFromGameFile:
13080 gameInfo.event = StrSave("Game from non-PGN file");
13081 gameInfo.site = StrSave(HostName());
13082 gameInfo.date = PGNDate();
13083 gameInfo.round = StrSave("-");
13084 gameInfo.white = StrSave("?");
13085 gameInfo.black = StrSave("?");
13094 ReplaceComment(index, text)
13100 while (*text == '\n') text++;
13101 len = strlen(text);
13102 while (len > 0 && text[len - 1] == '\n') len--;
13104 if (commentList[index] != NULL)
13105 free(commentList[index]);
13108 commentList[index] = NULL;
13111 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13112 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13113 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13114 commentList[index] = (char *) malloc(len + 2);
13115 strncpy(commentList[index], text, len);
13116 commentList[index][len] = '\n';
13117 commentList[index][len + 1] = NULLCHAR;
13119 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13121 commentList[index] = (char *) malloc(len + 6);
13122 strcpy(commentList[index], "{\n");
13123 strncpy(commentList[index]+2, text, len);
13124 commentList[index][len+2] = NULLCHAR;
13125 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13126 strcat(commentList[index], "\n}\n");
13140 if (ch == '\r') continue;
13142 } while (ch != '\0');
13146 AppendComment(index, text, addBraces)
13149 Boolean addBraces; // [HGM] braces: tells if we should add {}
13154 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13155 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13158 while (*text == '\n') text++;
13159 len = strlen(text);
13160 while (len > 0 && text[len - 1] == '\n') len--;
13162 if (len == 0) return;
13164 if (commentList[index] != NULL) {
13165 old = commentList[index];
13166 oldlen = strlen(old);
13167 while(commentList[index][oldlen-1] == '\n')
13168 commentList[index][--oldlen] = NULLCHAR;
13169 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13170 strcpy(commentList[index], old);
13172 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13173 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13174 if(addBraces) addBraces = FALSE; else { text++; len--; }
13175 while (*text == '\n') { text++; len--; }
13176 commentList[index][--oldlen] = NULLCHAR;
13178 if(addBraces) strcat(commentList[index], "\n{\n");
13179 else strcat(commentList[index], "\n");
13180 strcat(commentList[index], text);
13181 if(addBraces) strcat(commentList[index], "\n}\n");
13182 else strcat(commentList[index], "\n");
13184 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13186 strcpy(commentList[index], "{\n");
13187 else commentList[index][0] = NULLCHAR;
13188 strcat(commentList[index], text);
13189 strcat(commentList[index], "\n");
13190 if(addBraces) strcat(commentList[index], "}\n");
13194 static char * FindStr( char * text, char * sub_text )
13196 char * result = strstr( text, sub_text );
13198 if( result != NULL ) {
13199 result += strlen( sub_text );
13205 /* [AS] Try to extract PV info from PGN comment */
13206 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13207 char *GetInfoFromComment( int index, char * text )
13211 if( text != NULL && index > 0 ) {
13214 int time = -1, sec = 0, deci;
13215 char * s_eval = FindStr( text, "[%eval " );
13216 char * s_emt = FindStr( text, "[%emt " );
13218 if( s_eval != NULL || s_emt != NULL ) {
13222 if( s_eval != NULL ) {
13223 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13227 if( delim != ']' ) {
13232 if( s_emt != NULL ) {
13237 /* We expect something like: [+|-]nnn.nn/dd */
13240 if(*text != '{') return text; // [HGM] braces: must be normal comment
13242 sep = strchr( text, '/' );
13243 if( sep == NULL || sep < (text+4) ) {
13247 time = -1; sec = -1; deci = -1;
13248 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13249 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13250 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13251 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13255 if( score_lo < 0 || score_lo >= 100 ) {
13259 if(sec >= 0) time = 600*time + 10*sec; else
13260 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13262 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13264 /* [HGM] PV time: now locate end of PV info */
13265 while( *++sep >= '0' && *sep <= '9'); // strip depth
13267 while( *++sep >= '0' && *sep <= '9'); // strip time
13269 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13271 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13272 while(*sep == ' ') sep++;
13283 pvInfoList[index-1].depth = depth;
13284 pvInfoList[index-1].score = score;
13285 pvInfoList[index-1].time = 10*time; // centi-sec
13286 if(*sep == '}') *sep = 0; else *--sep = '{';
13292 SendToProgram(message, cps)
13294 ChessProgramState *cps;
13296 int count, outCount, error;
13299 if (cps->pr == NULL) return;
13302 if (appData.debugMode) {
13305 fprintf(debugFP, "%ld >%-6s: %s",
13306 SubtractTimeMarks(&now, &programStartTime),
13307 cps->which, message);
13310 count = strlen(message);
13311 outCount = OutputToProcess(cps->pr, message, count, &error);
13312 if (outCount < count && !exiting
13313 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13314 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13315 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13316 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13317 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13318 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13320 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13322 gameInfo.resultDetails = StrSave(buf);
13324 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13329 ReceiveFromProgram(isr, closure, message, count, error)
13330 InputSourceRef isr;
13338 ChessProgramState *cps = (ChessProgramState *)closure;
13340 if (isr != cps->isr) return; /* Killed intentionally */
13344 _("Error: %s chess program (%s) exited unexpectedly"),
13345 cps->which, cps->program);
13346 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13347 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13348 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13349 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13351 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13353 gameInfo.resultDetails = StrSave(buf);
13355 RemoveInputSource(cps->isr);
13356 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13359 _("Error reading from %s chess program (%s)"),
13360 cps->which, cps->program);
13361 RemoveInputSource(cps->isr);
13363 /* [AS] Program is misbehaving badly... kill it */
13364 if( count == -2 ) {
13365 DestroyChildProcess( cps->pr, 9 );
13369 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13374 if ((end_str = strchr(message, '\r')) != NULL)
13375 *end_str = NULLCHAR;
13376 if ((end_str = strchr(message, '\n')) != NULL)
13377 *end_str = NULLCHAR;
13379 if (appData.debugMode) {
13380 TimeMark now; int print = 1;
13381 char *quote = ""; char c; int i;
13383 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13384 char start = message[0];
13385 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13386 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13387 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13388 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13389 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13390 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13391 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13392 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13393 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13394 print = (appData.engineComments >= 2);
13396 message[0] = start; // restore original message
13400 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13401 SubtractTimeMarks(&now, &programStartTime), cps->which,
13407 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13408 if (appData.icsEngineAnalyze) {
13409 if (strstr(message, "whisper") != NULL ||
13410 strstr(message, "kibitz") != NULL ||
13411 strstr(message, "tellics") != NULL) return;
13414 HandleMachineMove(message, cps);
13419 SendTimeControl(cps, mps, tc, inc, sd, st)
13420 ChessProgramState *cps;
13421 int mps, inc, sd, st;
13427 if( timeControl_2 > 0 ) {
13428 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13429 tc = timeControl_2;
13432 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13433 inc /= cps->timeOdds;
13434 st /= cps->timeOdds;
13436 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13439 /* Set exact time per move, normally using st command */
13440 if (cps->stKludge) {
13441 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13443 if (seconds == 0) {
13444 sprintf(buf, "level 1 %d\n", st/60);
13446 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13449 sprintf(buf, "st %d\n", st);
13452 /* Set conventional or incremental time control, using level command */
13453 if (seconds == 0) {
13454 /* Note old gnuchess bug -- minutes:seconds used to not work.
13455 Fixed in later versions, but still avoid :seconds
13456 when seconds is 0. */
13457 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13459 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13460 seconds, inc/1000);
13463 SendToProgram(buf, cps);
13465 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13466 /* Orthogonally, limit search to given depth */
13468 if (cps->sdKludge) {
13469 sprintf(buf, "depth\n%d\n", sd);
13471 sprintf(buf, "sd %d\n", sd);
13473 SendToProgram(buf, cps);
13476 if(cps->nps > 0) { /* [HGM] nps */
13477 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13479 sprintf(buf, "nps %d\n", cps->nps);
13480 SendToProgram(buf, cps);
13485 ChessProgramState *WhitePlayer()
13486 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13488 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13489 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13495 SendTimeRemaining(cps, machineWhite)
13496 ChessProgramState *cps;
13497 int /*boolean*/ machineWhite;
13499 char message[MSG_SIZ];
13502 /* Note: this routine must be called when the clocks are stopped
13503 or when they have *just* been set or switched; otherwise
13504 it will be off by the time since the current tick started.
13506 if (machineWhite) {
13507 time = whiteTimeRemaining / 10;
13508 otime = blackTimeRemaining / 10;
13510 time = blackTimeRemaining / 10;
13511 otime = whiteTimeRemaining / 10;
13513 /* [HGM] translate opponent's time by time-odds factor */
13514 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13515 if (appData.debugMode) {
13516 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13519 if (time <= 0) time = 1;
13520 if (otime <= 0) otime = 1;
13522 sprintf(message, "time %ld\n", time);
13523 SendToProgram(message, cps);
13525 sprintf(message, "otim %ld\n", otime);
13526 SendToProgram(message, cps);
13530 BoolFeature(p, name, loc, cps)
13534 ChessProgramState *cps;
13537 int len = strlen(name);
13539 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13541 sscanf(*p, "%d", &val);
13543 while (**p && **p != ' ') (*p)++;
13544 sprintf(buf, "accepted %s\n", name);
13545 SendToProgram(buf, cps);
13552 IntFeature(p, name, loc, cps)
13556 ChessProgramState *cps;
13559 int len = strlen(name);
13560 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13562 sscanf(*p, "%d", loc);
13563 while (**p && **p != ' ') (*p)++;
13564 sprintf(buf, "accepted %s\n", name);
13565 SendToProgram(buf, cps);
13572 StringFeature(p, name, loc, cps)
13576 ChessProgramState *cps;
13579 int len = strlen(name);
13580 if (strncmp((*p), name, len) == 0
13581 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13583 sscanf(*p, "%[^\"]", loc);
13584 while (**p && **p != '\"') (*p)++;
13585 if (**p == '\"') (*p)++;
13586 sprintf(buf, "accepted %s\n", name);
13587 SendToProgram(buf, cps);
13594 ParseOption(Option *opt, ChessProgramState *cps)
13595 // [HGM] options: process the string that defines an engine option, and determine
13596 // name, type, default value, and allowed value range
13598 char *p, *q, buf[MSG_SIZ];
13599 int n, min = (-1)<<31, max = 1<<31, def;
13601 if(p = strstr(opt->name, " -spin ")) {
13602 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13603 if(max < min) max = min; // enforce consistency
13604 if(def < min) def = min;
13605 if(def > max) def = max;
13610 } else if((p = strstr(opt->name, " -slider "))) {
13611 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13612 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13613 if(max < min) max = min; // enforce consistency
13614 if(def < min) def = min;
13615 if(def > max) def = max;
13619 opt->type = Spin; // Slider;
13620 } else if((p = strstr(opt->name, " -string "))) {
13621 opt->textValue = p+9;
13622 opt->type = TextBox;
13623 } else if((p = strstr(opt->name, " -file "))) {
13624 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13625 opt->textValue = p+7;
13626 opt->type = TextBox; // FileName;
13627 } else if((p = strstr(opt->name, " -path "))) {
13628 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13629 opt->textValue = p+7;
13630 opt->type = TextBox; // PathName;
13631 } else if(p = strstr(opt->name, " -check ")) {
13632 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13633 opt->value = (def != 0);
13634 opt->type = CheckBox;
13635 } else if(p = strstr(opt->name, " -combo ")) {
13636 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13637 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13638 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13639 opt->value = n = 0;
13640 while(q = StrStr(q, " /// ")) {
13641 n++; *q = 0; // count choices, and null-terminate each of them
13643 if(*q == '*') { // remember default, which is marked with * prefix
13647 cps->comboList[cps->comboCnt++] = q;
13649 cps->comboList[cps->comboCnt++] = NULL;
13651 opt->type = ComboBox;
13652 } else if(p = strstr(opt->name, " -button")) {
13653 opt->type = Button;
13654 } else if(p = strstr(opt->name, " -save")) {
13655 opt->type = SaveButton;
13656 } else return FALSE;
13657 *p = 0; // terminate option name
13658 // now look if the command-line options define a setting for this engine option.
13659 if(cps->optionSettings && cps->optionSettings[0])
13660 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13661 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13662 sprintf(buf, "option %s", p);
13663 if(p = strstr(buf, ",")) *p = 0;
13665 SendToProgram(buf, cps);
13671 FeatureDone(cps, val)
13672 ChessProgramState* cps;
13675 DelayedEventCallback cb = GetDelayedEvent();
13676 if ((cb == InitBackEnd3 && cps == &first) ||
13677 (cb == TwoMachinesEventIfReady && cps == &second)) {
13678 CancelDelayedEvent();
13679 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13681 cps->initDone = val;
13684 /* Parse feature command from engine */
13686 ParseFeatures(args, cps)
13688 ChessProgramState *cps;
13696 while (*p == ' ') p++;
13697 if (*p == NULLCHAR) return;
13699 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13700 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13701 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13702 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13703 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13704 if (BoolFeature(&p, "reuse", &val, cps)) {
13705 /* Engine can disable reuse, but can't enable it if user said no */
13706 if (!val) cps->reuse = FALSE;
13709 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13710 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13711 if (gameMode == TwoMachinesPlay) {
13712 DisplayTwoMachinesTitle();
13718 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13719 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13720 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13721 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13722 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13723 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13724 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13725 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13726 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13727 if (IntFeature(&p, "done", &val, cps)) {
13728 FeatureDone(cps, val);
13731 /* Added by Tord: */
13732 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13733 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13734 /* End of additions by Tord */
13736 /* [HGM] added features: */
13737 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13738 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13739 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13740 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13741 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13742 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13743 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13744 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13745 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13746 SendToProgram(buf, cps);
13749 if(cps->nrOptions >= MAX_OPTIONS) {
13751 sprintf(buf, "%s engine has too many options\n", cps->which);
13752 DisplayError(buf, 0);
13756 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13757 /* End of additions by HGM */
13759 /* unknown feature: complain and skip */
13761 while (*q && *q != '=') q++;
13762 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13763 SendToProgram(buf, cps);
13769 while (*p && *p != '\"') p++;
13770 if (*p == '\"') p++;
13772 while (*p && *p != ' ') p++;
13780 PeriodicUpdatesEvent(newState)
13783 if (newState == appData.periodicUpdates)
13786 appData.periodicUpdates=newState;
13788 /* Display type changes, so update it now */
13789 // DisplayAnalysis();
13791 /* Get the ball rolling again... */
13793 AnalysisPeriodicEvent(1);
13794 StartAnalysisClock();
13799 PonderNextMoveEvent(newState)
13802 if (newState == appData.ponderNextMove) return;
13803 if (gameMode == EditPosition) EditPositionDone(TRUE);
13805 SendToProgram("hard\n", &first);
13806 if (gameMode == TwoMachinesPlay) {
13807 SendToProgram("hard\n", &second);
13810 SendToProgram("easy\n", &first);
13811 thinkOutput[0] = NULLCHAR;
13812 if (gameMode == TwoMachinesPlay) {
13813 SendToProgram("easy\n", &second);
13816 appData.ponderNextMove = newState;
13820 NewSettingEvent(option, command, value)
13826 if (gameMode == EditPosition) EditPositionDone(TRUE);
13827 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13828 SendToProgram(buf, &first);
13829 if (gameMode == TwoMachinesPlay) {
13830 SendToProgram(buf, &second);
13835 ShowThinkingEvent()
13836 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13838 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13839 int newState = appData.showThinking
13840 // [HGM] thinking: other features now need thinking output as well
13841 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13843 if (oldState == newState) return;
13844 oldState = newState;
13845 if (gameMode == EditPosition) EditPositionDone(TRUE);
13847 SendToProgram("post\n", &first);
13848 if (gameMode == TwoMachinesPlay) {
13849 SendToProgram("post\n", &second);
13852 SendToProgram("nopost\n", &first);
13853 thinkOutput[0] = NULLCHAR;
13854 if (gameMode == TwoMachinesPlay) {
13855 SendToProgram("nopost\n", &second);
13858 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13862 AskQuestionEvent(title, question, replyPrefix, which)
13863 char *title; char *question; char *replyPrefix; char *which;
13865 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13866 if (pr == NoProc) return;
13867 AskQuestion(title, question, replyPrefix, pr);
13871 DisplayMove(moveNumber)
13874 char message[MSG_SIZ];
13876 char cpThinkOutput[MSG_SIZ];
13878 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13880 if (moveNumber == forwardMostMove - 1 ||
13881 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13883 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13885 if (strchr(cpThinkOutput, '\n')) {
13886 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13889 *cpThinkOutput = NULLCHAR;
13892 /* [AS] Hide thinking from human user */
13893 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13894 *cpThinkOutput = NULLCHAR;
13895 if( thinkOutput[0] != NULLCHAR ) {
13898 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13899 cpThinkOutput[i] = '.';
13901 cpThinkOutput[i] = NULLCHAR;
13902 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13906 if (moveNumber == forwardMostMove - 1 &&
13907 gameInfo.resultDetails != NULL) {
13908 if (gameInfo.resultDetails[0] == NULLCHAR) {
13909 sprintf(res, " %s", PGNResult(gameInfo.result));
13911 sprintf(res, " {%s} %s",
13912 gameInfo.resultDetails, PGNResult(gameInfo.result));
13918 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13919 DisplayMessage(res, cpThinkOutput);
13921 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13922 WhiteOnMove(moveNumber) ? " " : ".. ",
13923 parseList[moveNumber], res);
13924 DisplayMessage(message, cpThinkOutput);
13929 DisplayComment(moveNumber, text)
13933 char title[MSG_SIZ];
13934 char buf[8000]; // comment can be long!
13937 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13938 strcpy(title, "Comment");
13940 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13941 WhiteOnMove(moveNumber) ? " " : ".. ",
13942 parseList[moveNumber]);
13944 // [HGM] PV info: display PV info together with (or as) comment
13945 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13946 if(text == NULL) text = "";
13947 score = pvInfoList[moveNumber].score;
13948 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13949 depth, (pvInfoList[moveNumber].time+50)/100, text);
13952 if (text != NULL && (appData.autoDisplayComment || commentUp))
13953 CommentPopUp(title, text);
13956 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13957 * might be busy thinking or pondering. It can be omitted if your
13958 * gnuchess is configured to stop thinking immediately on any user
13959 * input. However, that gnuchess feature depends on the FIONREAD
13960 * ioctl, which does not work properly on some flavors of Unix.
13964 ChessProgramState *cps;
13967 if (!cps->useSigint) return;
13968 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13969 switch (gameMode) {
13970 case MachinePlaysWhite:
13971 case MachinePlaysBlack:
13972 case TwoMachinesPlay:
13973 case IcsPlayingWhite:
13974 case IcsPlayingBlack:
13977 /* Skip if we know it isn't thinking */
13978 if (!cps->maybeThinking) return;
13979 if (appData.debugMode)
13980 fprintf(debugFP, "Interrupting %s\n", cps->which);
13981 InterruptChildProcess(cps->pr);
13982 cps->maybeThinking = FALSE;
13987 #endif /*ATTENTION*/
13993 if (whiteTimeRemaining <= 0) {
13996 if (appData.icsActive) {
13997 if (appData.autoCallFlag &&
13998 gameMode == IcsPlayingBlack && !blackFlag) {
13999 SendToICS(ics_prefix);
14000 SendToICS("flag\n");
14004 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14006 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14007 if (appData.autoCallFlag) {
14008 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14015 if (blackTimeRemaining <= 0) {
14018 if (appData.icsActive) {
14019 if (appData.autoCallFlag &&
14020 gameMode == IcsPlayingWhite && !whiteFlag) {
14021 SendToICS(ics_prefix);
14022 SendToICS("flag\n");
14026 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14028 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14029 if (appData.autoCallFlag) {
14030 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14043 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14044 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14047 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14049 if ( !WhiteOnMove(forwardMostMove) )
14050 /* White made time control */
14051 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14052 /* [HGM] time odds: correct new time quota for time odds! */
14053 / WhitePlayer()->timeOdds;
14055 /* Black made time control */
14056 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14057 / WhitePlayer()->other->timeOdds;
14061 DisplayBothClocks()
14063 int wom = gameMode == EditPosition ?
14064 !blackPlaysFirst : WhiteOnMove(currentMove);
14065 DisplayWhiteClock(whiteTimeRemaining, wom);
14066 DisplayBlackClock(blackTimeRemaining, !wom);
14070 /* Timekeeping seems to be a portability nightmare. I think everyone
14071 has ftime(), but I'm really not sure, so I'm including some ifdefs
14072 to use other calls if you don't. Clocks will be less accurate if
14073 you have neither ftime nor gettimeofday.
14076 /* VS 2008 requires the #include outside of the function */
14077 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14078 #include <sys/timeb.h>
14081 /* Get the current time as a TimeMark */
14086 #if HAVE_GETTIMEOFDAY
14088 struct timeval timeVal;
14089 struct timezone timeZone;
14091 gettimeofday(&timeVal, &timeZone);
14092 tm->sec = (long) timeVal.tv_sec;
14093 tm->ms = (int) (timeVal.tv_usec / 1000L);
14095 #else /*!HAVE_GETTIMEOFDAY*/
14098 // include <sys/timeb.h> / moved to just above start of function
14099 struct timeb timeB;
14102 tm->sec = (long) timeB.time;
14103 tm->ms = (int) timeB.millitm;
14105 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14106 tm->sec = (long) time(NULL);
14112 /* Return the difference in milliseconds between two
14113 time marks. We assume the difference will fit in a long!
14116 SubtractTimeMarks(tm2, tm1)
14117 TimeMark *tm2, *tm1;
14119 return 1000L*(tm2->sec - tm1->sec) +
14120 (long) (tm2->ms - tm1->ms);
14125 * Code to manage the game clocks.
14127 * In tournament play, black starts the clock and then white makes a move.
14128 * We give the human user a slight advantage if he is playing white---the
14129 * clocks don't run until he makes his first move, so it takes zero time.
14130 * Also, we don't account for network lag, so we could get out of sync
14131 * with GNU Chess's clock -- but then, referees are always right.
14134 static TimeMark tickStartTM;
14135 static long intendedTickLength;
14138 NextTickLength(timeRemaining)
14139 long timeRemaining;
14141 long nominalTickLength, nextTickLength;
14143 if (timeRemaining > 0L && timeRemaining <= 10000L)
14144 nominalTickLength = 100L;
14146 nominalTickLength = 1000L;
14147 nextTickLength = timeRemaining % nominalTickLength;
14148 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14150 return nextTickLength;
14153 /* Adjust clock one minute up or down */
14155 AdjustClock(Boolean which, int dir)
14157 if(which) blackTimeRemaining += 60000*dir;
14158 else whiteTimeRemaining += 60000*dir;
14159 DisplayBothClocks();
14162 /* Stop clocks and reset to a fresh time control */
14166 (void) StopClockTimer();
14167 if (appData.icsActive) {
14168 whiteTimeRemaining = blackTimeRemaining = 0;
14169 } else if (searchTime) {
14170 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14171 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14172 } else { /* [HGM] correct new time quote for time odds */
14173 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14174 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14176 if (whiteFlag || blackFlag) {
14178 whiteFlag = blackFlag = FALSE;
14180 DisplayBothClocks();
14183 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14185 /* Decrement running clock by amount of time that has passed */
14189 long timeRemaining;
14190 long lastTickLength, fudge;
14193 if (!appData.clockMode) return;
14194 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14198 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14200 /* Fudge if we woke up a little too soon */
14201 fudge = intendedTickLength - lastTickLength;
14202 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14204 if (WhiteOnMove(forwardMostMove)) {
14205 if(whiteNPS >= 0) lastTickLength = 0;
14206 timeRemaining = whiteTimeRemaining -= lastTickLength;
14207 DisplayWhiteClock(whiteTimeRemaining - fudge,
14208 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14210 if(blackNPS >= 0) lastTickLength = 0;
14211 timeRemaining = blackTimeRemaining -= lastTickLength;
14212 DisplayBlackClock(blackTimeRemaining - fudge,
14213 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14216 if (CheckFlags()) return;
14219 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14220 StartClockTimer(intendedTickLength);
14222 /* if the time remaining has fallen below the alarm threshold, sound the
14223 * alarm. if the alarm has sounded and (due to a takeback or time control
14224 * with increment) the time remaining has increased to a level above the
14225 * threshold, reset the alarm so it can sound again.
14228 if (appData.icsActive && appData.icsAlarm) {
14230 /* make sure we are dealing with the user's clock */
14231 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14232 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14235 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14236 alarmSounded = FALSE;
14237 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14239 alarmSounded = TRUE;
14245 /* A player has just moved, so stop the previously running
14246 clock and (if in clock mode) start the other one.
14247 We redisplay both clocks in case we're in ICS mode, because
14248 ICS gives us an update to both clocks after every move.
14249 Note that this routine is called *after* forwardMostMove
14250 is updated, so the last fractional tick must be subtracted
14251 from the color that is *not* on move now.
14254 SwitchClocks(int newMoveNr)
14256 long lastTickLength;
14258 int flagged = FALSE;
14262 if (StopClockTimer() && appData.clockMode) {
14263 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14264 if (!WhiteOnMove(forwardMostMove)) {
14265 if(blackNPS >= 0) lastTickLength = 0;
14266 blackTimeRemaining -= lastTickLength;
14267 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14268 // if(pvInfoList[forwardMostMove-1].time == -1)
14269 pvInfoList[forwardMostMove-1].time = // use GUI time
14270 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14272 if(whiteNPS >= 0) lastTickLength = 0;
14273 whiteTimeRemaining -= lastTickLength;
14274 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14275 // if(pvInfoList[forwardMostMove-1].time == -1)
14276 pvInfoList[forwardMostMove-1].time =
14277 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14279 flagged = CheckFlags();
14281 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14282 CheckTimeControl();
14284 if (flagged || !appData.clockMode) return;
14286 switch (gameMode) {
14287 case MachinePlaysBlack:
14288 case MachinePlaysWhite:
14289 case BeginningOfGame:
14290 if (pausing) return;
14294 case PlayFromGameFile:
14302 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14303 if(WhiteOnMove(forwardMostMove))
14304 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14305 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14309 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14310 whiteTimeRemaining : blackTimeRemaining);
14311 StartClockTimer(intendedTickLength);
14315 /* Stop both clocks */
14319 long lastTickLength;
14322 if (!StopClockTimer()) return;
14323 if (!appData.clockMode) return;
14327 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14328 if (WhiteOnMove(forwardMostMove)) {
14329 if(whiteNPS >= 0) lastTickLength = 0;
14330 whiteTimeRemaining -= lastTickLength;
14331 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14333 if(blackNPS >= 0) lastTickLength = 0;
14334 blackTimeRemaining -= lastTickLength;
14335 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14340 /* Start clock of player on move. Time may have been reset, so
14341 if clock is already running, stop and restart it. */
14345 (void) StopClockTimer(); /* in case it was running already */
14346 DisplayBothClocks();
14347 if (CheckFlags()) return;
14349 if (!appData.clockMode) return;
14350 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14352 GetTimeMark(&tickStartTM);
14353 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14354 whiteTimeRemaining : blackTimeRemaining);
14356 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14357 whiteNPS = blackNPS = -1;
14358 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14359 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14360 whiteNPS = first.nps;
14361 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14362 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14363 blackNPS = first.nps;
14364 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14365 whiteNPS = second.nps;
14366 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14367 blackNPS = second.nps;
14368 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14370 StartClockTimer(intendedTickLength);
14377 long second, minute, hour, day;
14379 static char buf[32];
14381 if (ms > 0 && ms <= 9900) {
14382 /* convert milliseconds to tenths, rounding up */
14383 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14385 sprintf(buf, " %03.1f ", tenths/10.0);
14389 /* convert milliseconds to seconds, rounding up */
14390 /* use floating point to avoid strangeness of integer division
14391 with negative dividends on many machines */
14392 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14399 day = second / (60 * 60 * 24);
14400 second = second % (60 * 60 * 24);
14401 hour = second / (60 * 60);
14402 second = second % (60 * 60);
14403 minute = second / 60;
14404 second = second % 60;
14407 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14408 sign, day, hour, minute, second);
14410 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14412 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14419 * This is necessary because some C libraries aren't ANSI C compliant yet.
14422 StrStr(string, match)
14423 char *string, *match;
14427 length = strlen(match);
14429 for (i = strlen(string) - length; i >= 0; i--, string++)
14430 if (!strncmp(match, string, length))
14437 StrCaseStr(string, match)
14438 char *string, *match;
14442 length = strlen(match);
14444 for (i = strlen(string) - length; i >= 0; i--, string++) {
14445 for (j = 0; j < length; j++) {
14446 if (ToLower(match[j]) != ToLower(string[j]))
14449 if (j == length) return string;
14463 c1 = ToLower(*s1++);
14464 c2 = ToLower(*s2++);
14465 if (c1 > c2) return 1;
14466 if (c1 < c2) return -1;
14467 if (c1 == NULLCHAR) return 0;
14476 return isupper(c) ? tolower(c) : c;
14484 return islower(c) ? toupper(c) : c;
14486 #endif /* !_amigados */
14494 if ((ret = (char *) malloc(strlen(s) + 1))) {
14501 StrSavePtr(s, savePtr)
14502 char *s, **savePtr;
14507 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14508 strcpy(*savePtr, s);
14520 clock = time((time_t *)NULL);
14521 tm = localtime(&clock);
14522 sprintf(buf, "%04d.%02d.%02d",
14523 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14524 return StrSave(buf);
14529 PositionToFEN(move, overrideCastling)
14531 char *overrideCastling;
14533 int i, j, fromX, fromY, toX, toY;
14540 whiteToPlay = (gameMode == EditPosition) ?
14541 !blackPlaysFirst : (move % 2 == 0);
14544 /* Piece placement data */
14545 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14547 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14548 if (boards[move][i][j] == EmptySquare) {
14550 } else { ChessSquare piece = boards[move][i][j];
14551 if (emptycount > 0) {
14552 if(emptycount<10) /* [HGM] can be >= 10 */
14553 *p++ = '0' + emptycount;
14554 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14557 if(PieceToChar(piece) == '+') {
14558 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14560 piece = (ChessSquare)(DEMOTED piece);
14562 *p++ = PieceToChar(piece);
14564 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14565 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14570 if (emptycount > 0) {
14571 if(emptycount<10) /* [HGM] can be >= 10 */
14572 *p++ = '0' + emptycount;
14573 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14580 /* [HGM] print Crazyhouse or Shogi holdings */
14581 if( gameInfo.holdingsWidth ) {
14582 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14584 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14585 piece = boards[move][i][BOARD_WIDTH-1];
14586 if( piece != EmptySquare )
14587 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14588 *p++ = PieceToChar(piece);
14590 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14591 piece = boards[move][BOARD_HEIGHT-i-1][0];
14592 if( piece != EmptySquare )
14593 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14594 *p++ = PieceToChar(piece);
14597 if( q == p ) *p++ = '-';
14603 *p++ = whiteToPlay ? 'w' : 'b';
14606 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14607 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14609 if(nrCastlingRights) {
14611 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14612 /* [HGM] write directly from rights */
14613 if(boards[move][CASTLING][2] != NoRights &&
14614 boards[move][CASTLING][0] != NoRights )
14615 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14616 if(boards[move][CASTLING][2] != NoRights &&
14617 boards[move][CASTLING][1] != NoRights )
14618 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14619 if(boards[move][CASTLING][5] != NoRights &&
14620 boards[move][CASTLING][3] != NoRights )
14621 *p++ = boards[move][CASTLING][3] + AAA;
14622 if(boards[move][CASTLING][5] != NoRights &&
14623 boards[move][CASTLING][4] != NoRights )
14624 *p++ = boards[move][CASTLING][4] + AAA;
14627 /* [HGM] write true castling rights */
14628 if( nrCastlingRights == 6 ) {
14629 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14630 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14631 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14632 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14633 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14634 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14635 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14636 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14639 if (q == p) *p++ = '-'; /* No castling rights */
14643 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14644 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14645 /* En passant target square */
14646 if (move > backwardMostMove) {
14647 fromX = moveList[move - 1][0] - AAA;
14648 fromY = moveList[move - 1][1] - ONE;
14649 toX = moveList[move - 1][2] - AAA;
14650 toY = moveList[move - 1][3] - ONE;
14651 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14652 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14653 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14655 /* 2-square pawn move just happened */
14657 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14661 } else if(move == backwardMostMove) {
14662 // [HGM] perhaps we should always do it like this, and forget the above?
14663 if((signed char)boards[move][EP_STATUS] >= 0) {
14664 *p++ = boards[move][EP_STATUS] + AAA;
14665 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14676 /* [HGM] find reversible plies */
14677 { int i = 0, j=move;
14679 if (appData.debugMode) { int k;
14680 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14681 for(k=backwardMostMove; k<=forwardMostMove; k++)
14682 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14686 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14687 if( j == backwardMostMove ) i += initialRulePlies;
14688 sprintf(p, "%d ", i);
14689 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14691 /* Fullmove number */
14692 sprintf(p, "%d", (move / 2) + 1);
14694 return StrSave(buf);
14698 ParseFEN(board, blackPlaysFirst, fen)
14700 int *blackPlaysFirst;
14710 /* [HGM] by default clear Crazyhouse holdings, if present */
14711 if(gameInfo.holdingsWidth) {
14712 for(i=0; i<BOARD_HEIGHT; i++) {
14713 board[i][0] = EmptySquare; /* black holdings */
14714 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14715 board[i][1] = (ChessSquare) 0; /* black counts */
14716 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14720 /* Piece placement data */
14721 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14724 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14725 if (*p == '/') p++;
14726 emptycount = gameInfo.boardWidth - j;
14727 while (emptycount--)
14728 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14730 #if(BOARD_FILES >= 10)
14731 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14732 p++; emptycount=10;
14733 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14734 while (emptycount--)
14735 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14737 } else if (isdigit(*p)) {
14738 emptycount = *p++ - '0';
14739 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14740 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14741 while (emptycount--)
14742 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14743 } else if (*p == '+' || isalpha(*p)) {
14744 if (j >= gameInfo.boardWidth) return FALSE;
14746 piece = CharToPiece(*++p);
14747 if(piece == EmptySquare) return FALSE; /* unknown piece */
14748 piece = (ChessSquare) (PROMOTED piece ); p++;
14749 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14750 } else piece = CharToPiece(*p++);
14752 if(piece==EmptySquare) return FALSE; /* unknown piece */
14753 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14754 piece = (ChessSquare) (PROMOTED piece);
14755 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14758 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14764 while (*p == '/' || *p == ' ') p++;
14766 /* [HGM] look for Crazyhouse holdings here */
14767 while(*p==' ') p++;
14768 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14770 if(*p == '-' ) *p++; /* empty holdings */ else {
14771 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14772 /* if we would allow FEN reading to set board size, we would */
14773 /* have to add holdings and shift the board read so far here */
14774 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14776 if((int) piece >= (int) BlackPawn ) {
14777 i = (int)piece - (int)BlackPawn;
14778 i = PieceToNumber((ChessSquare)i);
14779 if( i >= gameInfo.holdingsSize ) return FALSE;
14780 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14781 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14783 i = (int)piece - (int)WhitePawn;
14784 i = PieceToNumber((ChessSquare)i);
14785 if( i >= gameInfo.holdingsSize ) return FALSE;
14786 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14787 board[i][BOARD_WIDTH-2]++; /* black holdings */
14791 if(*p == ']') *p++;
14794 while(*p == ' ') p++;
14799 *blackPlaysFirst = FALSE;
14802 *blackPlaysFirst = TRUE;
14808 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14809 /* return the extra info in global variiables */
14811 /* set defaults in case FEN is incomplete */
14812 board[EP_STATUS] = EP_UNKNOWN;
14813 for(i=0; i<nrCastlingRights; i++ ) {
14814 board[CASTLING][i] =
14815 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14816 } /* assume possible unless obviously impossible */
14817 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14818 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14819 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14820 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14821 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14822 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14823 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14824 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14827 while(*p==' ') p++;
14828 if(nrCastlingRights) {
14829 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14830 /* castling indicator present, so default becomes no castlings */
14831 for(i=0; i<nrCastlingRights; i++ ) {
14832 board[CASTLING][i] = NoRights;
14835 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14836 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14837 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14838 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14839 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14841 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14842 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14843 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14845 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14846 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14847 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14848 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14849 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14850 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14853 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14854 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14855 board[CASTLING][2] = whiteKingFile;
14858 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14859 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14860 board[CASTLING][2] = whiteKingFile;
14863 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14864 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14865 board[CASTLING][5] = blackKingFile;
14868 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14869 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14870 board[CASTLING][5] = blackKingFile;
14873 default: /* FRC castlings */
14874 if(c >= 'a') { /* black rights */
14875 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14876 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14877 if(i == BOARD_RGHT) break;
14878 board[CASTLING][5] = i;
14880 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14881 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14883 board[CASTLING][3] = c;
14885 board[CASTLING][4] = c;
14886 } else { /* white rights */
14887 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14888 if(board[0][i] == WhiteKing) break;
14889 if(i == BOARD_RGHT) break;
14890 board[CASTLING][2] = i;
14891 c -= AAA - 'a' + 'A';
14892 if(board[0][c] >= WhiteKing) break;
14894 board[CASTLING][0] = c;
14896 board[CASTLING][1] = c;
14900 for(i=0; i<nrCastlingRights; i++)
14901 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14902 if (appData.debugMode) {
14903 fprintf(debugFP, "FEN castling rights:");
14904 for(i=0; i<nrCastlingRights; i++)
14905 fprintf(debugFP, " %d", board[CASTLING][i]);
14906 fprintf(debugFP, "\n");
14909 while(*p==' ') p++;
14912 /* read e.p. field in games that know e.p. capture */
14913 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14914 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14916 p++; board[EP_STATUS] = EP_NONE;
14918 char c = *p++ - AAA;
14920 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14921 if(*p >= '0' && *p <='9') *p++;
14922 board[EP_STATUS] = c;
14927 if(sscanf(p, "%d", &i) == 1) {
14928 FENrulePlies = i; /* 50-move ply counter */
14929 /* (The move number is still ignored) */
14936 EditPositionPasteFEN(char *fen)
14939 Board initial_position;
14941 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14942 DisplayError(_("Bad FEN position in clipboard"), 0);
14945 int savedBlackPlaysFirst = blackPlaysFirst;
14946 EditPositionEvent();
14947 blackPlaysFirst = savedBlackPlaysFirst;
14948 CopyBoard(boards[0], initial_position);
14949 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14950 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14951 DisplayBothClocks();
14952 DrawPosition(FALSE, boards[currentMove]);
14957 static char cseq[12] = "\\ ";
14959 Boolean set_cont_sequence(char *new_seq)
14964 // handle bad attempts to set the sequence
14966 return 0; // acceptable error - no debug
14968 len = strlen(new_seq);
14969 ret = (len > 0) && (len < sizeof(cseq));
14971 strcpy(cseq, new_seq);
14972 else if (appData.debugMode)
14973 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14978 reformat a source message so words don't cross the width boundary. internal
14979 newlines are not removed. returns the wrapped size (no null character unless
14980 included in source message). If dest is NULL, only calculate the size required
14981 for the dest buffer. lp argument indicats line position upon entry, and it's
14982 passed back upon exit.
14984 int wrap(char *dest, char *src, int count, int width, int *lp)
14986 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14988 cseq_len = strlen(cseq);
14989 old_line = line = *lp;
14990 ansi = len = clen = 0;
14992 for (i=0; i < count; i++)
14994 if (src[i] == '\033')
14997 // if we hit the width, back up
14998 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15000 // store i & len in case the word is too long
15001 old_i = i, old_len = len;
15003 // find the end of the last word
15004 while (i && src[i] != ' ' && src[i] != '\n')
15010 // word too long? restore i & len before splitting it
15011 if ((old_i-i+clen) >= width)
15018 if (i && src[i-1] == ' ')
15021 if (src[i] != ' ' && src[i] != '\n')
15028 // now append the newline and continuation sequence
15033 strncpy(dest+len, cseq, cseq_len);
15041 dest[len] = src[i];
15045 if (src[i] == '\n')
15050 if (dest && appData.debugMode)
15052 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15053 count, width, line, len, *lp);
15054 show_bytes(debugFP, src, count);
15055 fprintf(debugFP, "\ndest: ");
15056 show_bytes(debugFP, dest, len);
15057 fprintf(debugFP, "\n");
15059 *lp = dest ? line : old_line;
15064 // [HGM] vari: routines for shelving variations
15067 PushTail(int firstMove, int lastMove)
15069 int i, j, nrMoves = lastMove - firstMove;
15071 if(appData.icsActive) { // only in local mode
15072 forwardMostMove = currentMove; // mimic old ICS behavior
15075 if(storedGames >= MAX_VARIATIONS-1) return;
15077 // push current tail of game on stack
15078 savedResult[storedGames] = gameInfo.result;
15079 savedDetails[storedGames] = gameInfo.resultDetails;
15080 gameInfo.resultDetails = NULL;
15081 savedFirst[storedGames] = firstMove;
15082 savedLast [storedGames] = lastMove;
15083 savedFramePtr[storedGames] = framePtr;
15084 framePtr -= nrMoves; // reserve space for the boards
15085 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15086 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15087 for(j=0; j<MOVE_LEN; j++)
15088 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15089 for(j=0; j<2*MOVE_LEN; j++)
15090 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15091 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15092 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15093 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15094 pvInfoList[firstMove+i-1].depth = 0;
15095 commentList[framePtr+i] = commentList[firstMove+i];
15096 commentList[firstMove+i] = NULL;
15100 forwardMostMove = firstMove; // truncate game so we can start variation
15101 if(storedGames == 1) GreyRevert(FALSE);
15105 PopTail(Boolean annotate)
15108 char buf[8000], moveBuf[20];
15110 if(appData.icsActive) return FALSE; // only in local mode
15111 if(!storedGames) return FALSE; // sanity
15112 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15115 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15116 nrMoves = savedLast[storedGames] - currentMove;
15119 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15120 else strcpy(buf, "(");
15121 for(i=currentMove; i<forwardMostMove; i++) {
15123 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15124 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15125 strcat(buf, moveBuf);
15126 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15127 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15131 for(i=1; i<=nrMoves; i++) { // copy last variation back
15132 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15133 for(j=0; j<MOVE_LEN; j++)
15134 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15135 for(j=0; j<2*MOVE_LEN; j++)
15136 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15137 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15138 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15139 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15140 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15141 commentList[currentMove+i] = commentList[framePtr+i];
15142 commentList[framePtr+i] = NULL;
15144 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15145 framePtr = savedFramePtr[storedGames];
15146 gameInfo.result = savedResult[storedGames];
15147 if(gameInfo.resultDetails != NULL) {
15148 free(gameInfo.resultDetails);
15150 gameInfo.resultDetails = savedDetails[storedGames];
15151 forwardMostMove = currentMove + nrMoves;
15152 if(storedGames == 0) GreyRevert(TRUE);
15158 { // remove all shelved variations
15160 for(i=0; i<storedGames; i++) {
15161 if(savedDetails[i])
15162 free(savedDetails[i]);
15163 savedDetails[i] = NULL;
15165 for(i=framePtr; i<MAX_MOVES; i++) {
15166 if(commentList[i]) free(commentList[i]);
15167 commentList[i] = NULL;
15169 framePtr = MAX_MOVES-1;
15174 LoadVariation(int index, char *text)
15175 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15176 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15177 int level = 0, move;
15179 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15180 // first find outermost bracketing variation
15181 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15182 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15183 if(*p == '{') wait = '}'; else
15184 if(*p == '[') wait = ']'; else
15185 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15186 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15188 if(*p == wait) wait = NULLCHAR; // closing ]} found
15191 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15192 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15193 end[1] = NULLCHAR; // clip off comment beyond variation
15194 ToNrEvent(currentMove-1);
15195 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15196 // kludge: use ParsePV() to append variation to game
15197 move = currentMove;
15198 ParsePV(start, TRUE);
15199 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15200 ClearPremoveHighlights();
15202 ToNrEvent(currentMove+1);