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 char partnerStatus[MSG_SIZ];
248 Boolean originalFlip;
249 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
250 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
251 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
252 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
253 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
254 int opponentKibitzes;
255 int lastSavedGame; /* [HGM] save: ID of game */
256 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
257 extern int chatCount;
259 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
261 /* States for ics_getting_history */
263 #define H_REQUESTED 1
264 #define H_GOT_REQ_HEADER 2
265 #define H_GOT_UNREQ_HEADER 3
266 #define H_GETTING_MOVES 4
267 #define H_GOT_UNWANTED_HEADER 5
269 /* whosays values for GameEnds */
278 /* Maximum number of games in a cmail message */
279 #define CMAIL_MAX_GAMES 20
281 /* Different types of move when calling RegisterMove */
283 #define CMAIL_RESIGN 1
285 #define CMAIL_ACCEPT 3
287 /* Different types of result to remember for each game */
288 #define CMAIL_NOT_RESULT 0
289 #define CMAIL_OLD_RESULT 1
290 #define CMAIL_NEW_RESULT 2
292 /* Telnet protocol constants */
303 static char * safeStrCpy( char * dst, const char * src, size_t count )
305 assert( dst != NULL );
306 assert( src != NULL );
309 strncpy( dst, src, count );
310 dst[ count-1 ] = '\0';
314 /* Some compiler can't cast u64 to double
315 * This function do the job for us:
317 * We use the highest bit for cast, this only
318 * works if the highest bit is not
319 * in use (This should not happen)
321 * We used this for all compiler
324 u64ToDouble(u64 value)
327 u64 tmp = value & u64Const(0x7fffffffffffffff);
328 r = (double)(s64)tmp;
329 if (value & u64Const(0x8000000000000000))
330 r += 9.2233720368547758080e18; /* 2^63 */
334 /* Fake up flags for now, as we aren't keeping track of castling
335 availability yet. [HGM] Change of logic: the flag now only
336 indicates the type of castlings allowed by the rule of the game.
337 The actual rights themselves are maintained in the array
338 castlingRights, as part of the game history, and are not probed
344 int flags = F_ALL_CASTLE_OK;
345 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
346 switch (gameInfo.variant) {
348 flags &= ~F_ALL_CASTLE_OK;
349 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
350 flags |= F_IGNORE_CHECK;
352 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
355 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
357 case VariantKriegspiel:
358 flags |= F_KRIEGSPIEL_CAPTURE;
360 case VariantCapaRandom:
361 case VariantFischeRandom:
362 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
363 case VariantNoCastle:
364 case VariantShatranj:
367 flags &= ~F_ALL_CASTLE_OK;
375 FILE *gameFileFP, *debugFP;
378 [AS] Note: sometimes, the sscanf() function is used to parse the input
379 into a fixed-size buffer. Because of this, we must be prepared to
380 receive strings as long as the size of the input buffer, which is currently
381 set to 4K for Windows and 8K for the rest.
382 So, we must either allocate sufficiently large buffers here, or
383 reduce the size of the input buffer in the input reading part.
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
388 char thinkOutput1[MSG_SIZ*10];
390 ChessProgramState first, second;
392 /* premove variables */
395 int premoveFromX = 0;
396 int premoveFromY = 0;
397 int premovePromoChar = 0;
399 Boolean alarmSounded;
400 /* end premove variables */
402 char *ics_prefix = "$";
403 int ics_type = ICS_GENERIC;
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
406 int pauseExamForwardMostMove = 0;
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
413 int whiteFlag = FALSE, blackFlag = FALSE;
414 int userOfferedDraw = FALSE;
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
417 int cmailMoveType[CMAIL_MAX_GAMES];
418 long ics_clock_paused = 0;
419 ProcRef icsPR = NoProc, cmailPR = NoProc;
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
421 GameMode gameMode = BeginningOfGame;
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
425 int hiddenThinkOutputState = 0; /* [AS] */
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
427 int adjudicateLossPlies = 6;
428 char white_holding[64], black_holding[64];
429 TimeMark lastNodeCountTime;
430 long lastNodeCount=0;
431 int have_sent_ICS_logon = 0;
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
434 long timeControl_2; /* [AS] Allow separate time controls */
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
436 long timeRemaining[2][MAX_MOVES];
438 TimeMark programStartTime;
439 char ics_handle[MSG_SIZ];
440 int have_set_title = 0;
442 /* animateTraining preserves the state of appData.animate
443 * when Training mode is activated. This allows the
444 * response to be animated when appData.animate == TRUE and
445 * appData.animateDragging == TRUE.
447 Boolean animateTraining;
453 Board boards[MAX_MOVES];
454 /* [HGM] Following 7 needed for accurate legality tests: */
455 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
456 signed char initialRights[BOARD_FILES];
457 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
458 int initialRulePlies, FENrulePlies;
459 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
462 int mute; // mute all sounds
464 // [HGM] vari: next 12 to save and restore variations
465 #define MAX_VARIATIONS 10
466 int framePtr = MAX_MOVES-1; // points to free stack entry
468 int savedFirst[MAX_VARIATIONS];
469 int savedLast[MAX_VARIATIONS];
470 int savedFramePtr[MAX_VARIATIONS];
471 char *savedDetails[MAX_VARIATIONS];
472 ChessMove savedResult[MAX_VARIATIONS];
474 void PushTail P((int firstMove, int lastMove));
475 Boolean PopTail P((Boolean annotate));
476 void CleanupTail P((void));
478 ChessSquare FIDEArray[2][BOARD_FILES] = {
479 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482 BlackKing, BlackBishop, BlackKnight, BlackRook }
485 ChessSquare twoKingsArray[2][BOARD_FILES] = {
486 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
487 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
488 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
489 BlackKing, BlackKing, BlackKnight, BlackRook }
492 ChessSquare KnightmateArray[2][BOARD_FILES] = {
493 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
494 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
495 { BlackRook, BlackMan, BlackBishop, BlackQueen,
496 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
499 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
500 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
501 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
502 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
503 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
506 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
508 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
510 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
513 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
514 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
515 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackMan, BlackFerz,
517 BlackKing, BlackMan, BlackKnight, BlackRook }
521 #if (BOARD_FILES>=10)
522 ChessSquare ShogiArray[2][BOARD_FILES] = {
523 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
524 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
525 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
526 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
529 ChessSquare XiangqiArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
531 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
533 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
536 ChessSquare CapablancaArray[2][BOARD_FILES] = {
537 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
540 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
543 ChessSquare GreatArray[2][BOARD_FILES] = {
544 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
545 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
546 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
547 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
550 ChessSquare JanusArray[2][BOARD_FILES] = {
551 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
552 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
553 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
554 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
558 ChessSquare GothicArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
560 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
562 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
565 #define GothicArray CapablancaArray
569 ChessSquare FalconArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
571 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
573 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
576 #define FalconArray CapablancaArray
579 #else // !(BOARD_FILES>=10)
580 #define XiangqiPosition FIDEArray
581 #define CapablancaArray FIDEArray
582 #define GothicArray FIDEArray
583 #define GreatArray FIDEArray
584 #endif // !(BOARD_FILES>=10)
586 #if (BOARD_FILES>=12)
587 ChessSquare CourierArray[2][BOARD_FILES] = {
588 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
589 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
590 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
591 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
593 #else // !(BOARD_FILES>=12)
594 #define CourierArray CapablancaArray
595 #endif // !(BOARD_FILES>=12)
598 Board initialPosition;
601 /* Convert str to a rating. Checks for special cases of "----",
603 "++++", etc. Also strips ()'s */
605 string_to_rating(str)
608 while(*str && !isdigit(*str)) ++str;
610 return 0; /* One of the special "no rating" cases */
618 /* Init programStats */
619 programStats.movelist[0] = 0;
620 programStats.depth = 0;
621 programStats.nr_moves = 0;
622 programStats.moves_left = 0;
623 programStats.nodes = 0;
624 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
625 programStats.score = 0;
626 programStats.got_only_move = 0;
627 programStats.got_fail = 0;
628 programStats.line_is_book = 0;
634 int matched, min, sec;
636 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
638 GetTimeMark(&programStartTime);
639 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
642 programStats.ok_to_send = 1;
643 programStats.seen_stat = 0;
646 * Initialize game list
652 * Internet chess server status
654 if (appData.icsActive) {
655 appData.matchMode = FALSE;
656 appData.matchGames = 0;
658 appData.noChessProgram = !appData.zippyPlay;
660 appData.zippyPlay = FALSE;
661 appData.zippyTalk = FALSE;
662 appData.noChessProgram = TRUE;
664 if (*appData.icsHelper != NULLCHAR) {
665 appData.useTelnet = TRUE;
666 appData.telnetProgram = appData.icsHelper;
669 appData.zippyTalk = appData.zippyPlay = FALSE;
672 /* [AS] Initialize pv info list [HGM] and game state */
676 for( i=0; i<=framePtr; i++ ) {
677 pvInfoList[i].depth = -1;
678 boards[i][EP_STATUS] = EP_NONE;
679 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
684 * Parse timeControl resource
686 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
687 appData.movesPerSession)) {
689 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
690 DisplayFatalError(buf, 0, 2);
694 * Parse searchTime resource
696 if (*appData.searchTime != NULLCHAR) {
697 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
699 searchTime = min * 60;
700 } else if (matched == 2) {
701 searchTime = min * 60 + sec;
704 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
705 DisplayFatalError(buf, 0, 2);
709 /* [AS] Adjudication threshold */
710 adjudicateLossThreshold = appData.adjudicateLossThreshold;
712 first.which = "first";
713 second.which = "second";
714 first.maybeThinking = second.maybeThinking = FALSE;
715 first.pr = second.pr = NoProc;
716 first.isr = second.isr = NULL;
717 first.sendTime = second.sendTime = 2;
718 first.sendDrawOffers = 1;
719 if (appData.firstPlaysBlack) {
720 first.twoMachinesColor = "black\n";
721 second.twoMachinesColor = "white\n";
723 first.twoMachinesColor = "white\n";
724 second.twoMachinesColor = "black\n";
726 first.program = appData.firstChessProgram;
727 second.program = appData.secondChessProgram;
728 first.host = appData.firstHost;
729 second.host = appData.secondHost;
730 first.dir = appData.firstDirectory;
731 second.dir = appData.secondDirectory;
732 first.other = &second;
733 second.other = &first;
734 first.initString = appData.initString;
735 second.initString = appData.secondInitString;
736 first.computerString = appData.firstComputerString;
737 second.computerString = appData.secondComputerString;
738 first.useSigint = second.useSigint = TRUE;
739 first.useSigterm = second.useSigterm = TRUE;
740 first.reuse = appData.reuseFirst;
741 second.reuse = appData.reuseSecond;
742 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
743 second.nps = appData.secondNPS;
744 first.useSetboard = second.useSetboard = FALSE;
745 first.useSAN = second.useSAN = FALSE;
746 first.usePing = second.usePing = FALSE;
747 first.lastPing = second.lastPing = 0;
748 first.lastPong = second.lastPong = 0;
749 first.usePlayother = second.usePlayother = FALSE;
750 first.useColors = second.useColors = TRUE;
751 first.useUsermove = second.useUsermove = FALSE;
752 first.sendICS = second.sendICS = FALSE;
753 first.sendName = second.sendName = appData.icsActive;
754 first.sdKludge = second.sdKludge = FALSE;
755 first.stKludge = second.stKludge = FALSE;
756 TidyProgramName(first.program, first.host, first.tidy);
757 TidyProgramName(second.program, second.host, second.tidy);
758 first.matchWins = second.matchWins = 0;
759 strcpy(first.variants, appData.variant);
760 strcpy(second.variants, appData.variant);
761 first.analysisSupport = second.analysisSupport = 2; /* detect */
762 first.analyzing = second.analyzing = FALSE;
763 first.initDone = second.initDone = FALSE;
765 /* New features added by Tord: */
766 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
767 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
768 /* End of new features added by Tord. */
769 first.fenOverride = appData.fenOverride1;
770 second.fenOverride = appData.fenOverride2;
772 /* [HGM] time odds: set factor for each machine */
773 first.timeOdds = appData.firstTimeOdds;
774 second.timeOdds = appData.secondTimeOdds;
776 if(appData.timeOddsMode) {
777 norm = first.timeOdds;
778 if(norm > second.timeOdds) norm = second.timeOdds;
780 first.timeOdds /= norm;
781 second.timeOdds /= norm;
784 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
785 first.accumulateTC = appData.firstAccumulateTC;
786 second.accumulateTC = appData.secondAccumulateTC;
787 first.maxNrOfSessions = second.maxNrOfSessions = 1;
790 first.debug = second.debug = FALSE;
791 first.supportsNPS = second.supportsNPS = UNKNOWN;
794 first.optionSettings = appData.firstOptions;
795 second.optionSettings = appData.secondOptions;
797 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
798 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
799 first.isUCI = appData.firstIsUCI; /* [AS] */
800 second.isUCI = appData.secondIsUCI; /* [AS] */
801 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
802 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
804 if (appData.firstProtocolVersion > PROTOVER ||
805 appData.firstProtocolVersion < 1) {
807 sprintf(buf, _("protocol version %d not supported"),
808 appData.firstProtocolVersion);
809 DisplayFatalError(buf, 0, 2);
811 first.protocolVersion = appData.firstProtocolVersion;
814 if (appData.secondProtocolVersion > PROTOVER ||
815 appData.secondProtocolVersion < 1) {
817 sprintf(buf, _("protocol version %d not supported"),
818 appData.secondProtocolVersion);
819 DisplayFatalError(buf, 0, 2);
821 second.protocolVersion = appData.secondProtocolVersion;
824 if (appData.icsActive) {
825 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
826 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
827 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
828 appData.clockMode = FALSE;
829 first.sendTime = second.sendTime = 0;
833 /* Override some settings from environment variables, for backward
834 compatibility. Unfortunately it's not feasible to have the env
835 vars just set defaults, at least in xboard. Ugh.
837 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
842 if (appData.noChessProgram) {
843 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
844 sprintf(programVersion, "%s", PACKAGE_STRING);
846 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
847 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
848 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
851 if (!appData.icsActive) {
853 /* Check for variants that are supported only in ICS mode,
854 or not at all. Some that are accepted here nevertheless
855 have bugs; see comments below.
857 VariantClass variant = StringToVariant(appData.variant);
859 case VariantBughouse: /* need four players and two boards */
860 case VariantKriegspiel: /* need to hide pieces and move details */
861 /* case VariantFischeRandom: (Fabien: moved below) */
862 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
863 DisplayFatalError(buf, 0, 2);
867 case VariantLoadable:
877 sprintf(buf, _("Unknown variant name %s"), appData.variant);
878 DisplayFatalError(buf, 0, 2);
881 case VariantXiangqi: /* [HGM] repetition rules not implemented */
882 case VariantFairy: /* [HGM] TestLegality definitely off! */
883 case VariantGothic: /* [HGM] should work */
884 case VariantCapablanca: /* [HGM] should work */
885 case VariantCourier: /* [HGM] initial forced moves not implemented */
886 case VariantShogi: /* [HGM] drops not tested for legality */
887 case VariantKnightmate: /* [HGM] should work */
888 case VariantCylinder: /* [HGM] untested */
889 case VariantFalcon: /* [HGM] untested */
890 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
891 offboard interposition not understood */
892 case VariantNormal: /* definitely works! */
893 case VariantWildCastle: /* pieces not automatically shuffled */
894 case VariantNoCastle: /* pieces not automatically shuffled */
895 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
896 case VariantLosers: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantSuicide: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantGiveaway: /* should work except for win condition,
901 and doesn't know captures are mandatory */
902 case VariantTwoKings: /* should work */
903 case VariantAtomic: /* should work except for win condition */
904 case Variant3Check: /* should work except for win condition */
905 case VariantShatranj: /* should work except for all win conditions */
906 case VariantMakruk: /* should work except for daw countdown */
907 case VariantBerolina: /* might work if TestLegality is off */
908 case VariantCapaRandom: /* should work */
909 case VariantJanus: /* should work */
910 case VariantSuper: /* experimental */
911 case VariantGreat: /* experimental, requires legality testing to be off */
916 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
917 InitEngineUCI( installDir, &second );
920 int NextIntegerFromString( char ** str, long * value )
925 while( *s == ' ' || *s == '\t' ) {
931 if( *s >= '0' && *s <= '9' ) {
932 while( *s >= '0' && *s <= '9' ) {
933 *value = *value * 10 + (*s - '0');
945 int NextTimeControlFromString( char ** str, long * value )
948 int result = NextIntegerFromString( str, &temp );
951 *value = temp * 60; /* Minutes */
954 result = NextIntegerFromString( str, &temp );
955 *value += temp; /* Seconds */
962 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
963 { /* [HGM] routine added to read '+moves/time' for secondary time control */
964 int result = -1; long temp, temp2;
966 if(**str != '+') return -1; // old params remain in force!
968 if( NextTimeControlFromString( str, &temp ) ) return -1;
971 /* time only: incremental or sudden-death time control */
972 if(**str == '+') { /* increment follows; read it */
974 if(result = NextIntegerFromString( str, &temp2)) return -1;
977 *moves = 0; *tc = temp * 1000;
979 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
981 (*str)++; /* classical time control */
982 result = NextTimeControlFromString( str, &temp2);
991 int GetTimeQuota(int movenr)
992 { /* [HGM] get time to add from the multi-session time-control string */
993 int moves=1; /* kludge to force reading of first session */
994 long time, increment;
995 char *s = fullTimeControlString;
997 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
999 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1000 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1001 if(movenr == -1) return time; /* last move before new session */
1002 if(!moves) return increment; /* current session is incremental */
1003 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1004 } while(movenr >= -1); /* try again for next session */
1006 return 0; // no new time quota on this move
1010 ParseTimeControl(tc, ti, mps)
1019 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1022 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1023 else sprintf(buf, "+%s+%d", tc, ti);
1026 sprintf(buf, "+%d/%s", mps, tc);
1027 else sprintf(buf, "+%s", tc);
1029 fullTimeControlString = StrSave(buf);
1031 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1036 /* Parse second time control */
1039 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1047 timeControl_2 = tc2 * 1000;
1057 timeControl = tc1 * 1000;
1060 timeIncrement = ti * 1000; /* convert to ms */
1061 movesPerSession = 0;
1064 movesPerSession = mps;
1072 if (appData.debugMode) {
1073 fprintf(debugFP, "%s\n", programVersion);
1076 set_cont_sequence(appData.wrapContSeq);
1077 if (appData.matchGames > 0) {
1078 appData.matchMode = TRUE;
1079 } else if (appData.matchMode) {
1080 appData.matchGames = 1;
1082 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1083 appData.matchGames = appData.sameColorGames;
1084 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1085 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1086 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1089 if (appData.noChessProgram || first.protocolVersion == 1) {
1092 /* kludge: allow timeout for initial "feature" commands */
1094 DisplayMessage("", _("Starting chess program"));
1095 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1100 InitBackEnd3 P((void))
1102 GameMode initialMode;
1106 InitChessProgram(&first, startedFromSetupPosition);
1109 if (appData.icsActive) {
1111 /* [DM] Make a console window if needed [HGM] merged ifs */
1116 if (*appData.icsCommPort != NULLCHAR) {
1117 sprintf(buf, _("Could not open comm port %s"),
1118 appData.icsCommPort);
1120 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1121 appData.icsHost, appData.icsPort);
1123 DisplayFatalError(buf, err, 1);
1128 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1130 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1131 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1132 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1133 } else if (appData.noChessProgram) {
1139 if (*appData.cmailGameName != NULLCHAR) {
1141 OpenLoopback(&cmailPR);
1143 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1147 DisplayMessage("", "");
1148 if (StrCaseCmp(appData.initialMode, "") == 0) {
1149 initialMode = BeginningOfGame;
1150 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1151 initialMode = TwoMachinesPlay;
1152 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1153 initialMode = AnalyzeFile;
1154 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1155 initialMode = AnalyzeMode;
1156 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1157 initialMode = MachinePlaysWhite;
1158 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1159 initialMode = MachinePlaysBlack;
1160 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1161 initialMode = EditGame;
1162 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1163 initialMode = EditPosition;
1164 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1165 initialMode = Training;
1167 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1168 DisplayFatalError(buf, 0, 2);
1172 if (appData.matchMode) {
1173 /* Set up machine vs. machine match */
1174 if (appData.noChessProgram) {
1175 DisplayFatalError(_("Can't have a match with no chess programs"),
1181 if (*appData.loadGameFile != NULLCHAR) {
1182 int index = appData.loadGameIndex; // [HGM] autoinc
1183 if(index<0) lastIndex = index = 1;
1184 if (!LoadGameFromFile(appData.loadGameFile,
1186 appData.loadGameFile, FALSE)) {
1187 DisplayFatalError(_("Bad game file"), 0, 1);
1190 } else if (*appData.loadPositionFile != NULLCHAR) {
1191 int index = appData.loadPositionIndex; // [HGM] autoinc
1192 if(index<0) lastIndex = index = 1;
1193 if (!LoadPositionFromFile(appData.loadPositionFile,
1195 appData.loadPositionFile)) {
1196 DisplayFatalError(_("Bad position file"), 0, 1);
1201 } else if (*appData.cmailGameName != NULLCHAR) {
1202 /* Set up cmail mode */
1203 ReloadCmailMsgEvent(TRUE);
1205 /* Set up other modes */
1206 if (initialMode == AnalyzeFile) {
1207 if (*appData.loadGameFile == NULLCHAR) {
1208 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1212 if (*appData.loadGameFile != NULLCHAR) {
1213 (void) LoadGameFromFile(appData.loadGameFile,
1214 appData.loadGameIndex,
1215 appData.loadGameFile, TRUE);
1216 } else if (*appData.loadPositionFile != NULLCHAR) {
1217 (void) LoadPositionFromFile(appData.loadPositionFile,
1218 appData.loadPositionIndex,
1219 appData.loadPositionFile);
1220 /* [HGM] try to make self-starting even after FEN load */
1221 /* to allow automatic setup of fairy variants with wtm */
1222 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1223 gameMode = BeginningOfGame;
1224 setboardSpoiledMachineBlack = 1;
1226 /* [HGM] loadPos: make that every new game uses the setup */
1227 /* from file as long as we do not switch variant */
1228 if(!blackPlaysFirst) {
1229 startedFromPositionFile = TRUE;
1230 CopyBoard(filePosition, boards[0]);
1233 if (initialMode == AnalyzeMode) {
1234 if (appData.noChessProgram) {
1235 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1238 if (appData.icsActive) {
1239 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1243 } else if (initialMode == AnalyzeFile) {
1244 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1245 ShowThinkingEvent();
1247 AnalysisPeriodicEvent(1);
1248 } else if (initialMode == MachinePlaysWhite) {
1249 if (appData.noChessProgram) {
1250 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1254 if (appData.icsActive) {
1255 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1259 MachineWhiteEvent();
1260 } else if (initialMode == MachinePlaysBlack) {
1261 if (appData.noChessProgram) {
1262 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1266 if (appData.icsActive) {
1267 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1271 MachineBlackEvent();
1272 } else if (initialMode == TwoMachinesPlay) {
1273 if (appData.noChessProgram) {
1274 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1278 if (appData.icsActive) {
1279 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1284 } else if (initialMode == EditGame) {
1286 } else if (initialMode == EditPosition) {
1287 EditPositionEvent();
1288 } else if (initialMode == Training) {
1289 if (*appData.loadGameFile == NULLCHAR) {
1290 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299 * Establish will establish a contact to a remote host.port.
1300 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1301 * used to talk to the host.
1302 * Returns 0 if okay, error code if not.
1309 if (*appData.icsCommPort != NULLCHAR) {
1310 /* Talk to the host through a serial comm port */
1311 return OpenCommPort(appData.icsCommPort, &icsPR);
1313 } else if (*appData.gateway != NULLCHAR) {
1314 if (*appData.remoteShell == NULLCHAR) {
1315 /* Use the rcmd protocol to run telnet program on a gateway host */
1316 snprintf(buf, sizeof(buf), "%s %s %s",
1317 appData.telnetProgram, appData.icsHost, appData.icsPort);
1318 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1321 /* Use the rsh program to run telnet program on a gateway host */
1322 if (*appData.remoteUser == NULLCHAR) {
1323 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1324 appData.gateway, appData.telnetProgram,
1325 appData.icsHost, appData.icsPort);
1327 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1328 appData.remoteShell, appData.gateway,
1329 appData.remoteUser, appData.telnetProgram,
1330 appData.icsHost, appData.icsPort);
1332 return StartChildProcess(buf, "", &icsPR);
1335 } else if (appData.useTelnet) {
1336 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1339 /* TCP socket interface differs somewhat between
1340 Unix and NT; handle details in the front end.
1342 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1347 show_bytes(fp, buf, count)
1353 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1354 fprintf(fp, "\\%03o", *buf & 0xff);
1363 /* Returns an errno value */
1365 OutputMaybeTelnet(pr, message, count, outError)
1371 char buf[8192], *p, *q, *buflim;
1372 int left, newcount, outcount;
1374 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1375 *appData.gateway != NULLCHAR) {
1376 if (appData.debugMode) {
1377 fprintf(debugFP, ">ICS: ");
1378 show_bytes(debugFP, message, count);
1379 fprintf(debugFP, "\n");
1381 return OutputToProcess(pr, message, count, outError);
1384 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1404 } else if (((unsigned char) *p) == TN_IAC) {
1405 *q++ = (char) TN_IAC;
1412 if (appData.debugMode) {
1413 fprintf(debugFP, ">ICS: ");
1414 show_bytes(debugFP, buf, newcount);
1415 fprintf(debugFP, "\n");
1417 outcount = OutputToProcess(pr, buf, newcount, outError);
1418 if (outcount < newcount) return -1; /* to be sure */
1423 read_from_player(isr, closure, message, count, error)
1430 int outError, outCount;
1431 static int gotEof = 0;
1433 /* Pass data read from player on to ICS */
1436 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1437 if (outCount < count) {
1438 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1440 } else if (count < 0) {
1441 RemoveInputSource(isr);
1442 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1443 } else if (gotEof++ > 0) {
1444 RemoveInputSource(isr);
1445 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1452 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1453 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1454 SendToICS("date\n");
1455 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1458 /* added routine for printf style output to ics */
1459 void ics_printf(char *format, ...)
1461 char buffer[MSG_SIZ];
1464 va_start(args, format);
1465 vsnprintf(buffer, sizeof(buffer), format, args);
1466 buffer[sizeof(buffer)-1] = '\0';
1475 int count, outCount, outError;
1477 if (icsPR == NULL) return;
1480 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1481 if (outCount < count) {
1482 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1486 /* This is used for sending logon scripts to the ICS. Sending
1487 without a delay causes problems when using timestamp on ICC
1488 (at least on my machine). */
1490 SendToICSDelayed(s,msdelay)
1494 int count, outCount, outError;
1496 if (icsPR == NULL) return;
1499 if (appData.debugMode) {
1500 fprintf(debugFP, ">ICS: ");
1501 show_bytes(debugFP, s, count);
1502 fprintf(debugFP, "\n");
1504 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1506 if (outCount < count) {
1507 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1512 /* Remove all highlighting escape sequences in s
1513 Also deletes any suffix starting with '('
1516 StripHighlightAndTitle(s)
1519 static char retbuf[MSG_SIZ];
1522 while (*s != NULLCHAR) {
1523 while (*s == '\033') {
1524 while (*s != NULLCHAR && !isalpha(*s)) s++;
1525 if (*s != NULLCHAR) s++;
1527 while (*s != NULLCHAR && *s != '\033') {
1528 if (*s == '(' || *s == '[') {
1539 /* Remove all highlighting escape sequences in s */
1544 static char retbuf[MSG_SIZ];
1547 while (*s != NULLCHAR) {
1548 while (*s == '\033') {
1549 while (*s != NULLCHAR && !isalpha(*s)) s++;
1550 if (*s != NULLCHAR) s++;
1552 while (*s != NULLCHAR && *s != '\033') {
1560 char *variantNames[] = VARIANT_NAMES;
1565 return variantNames[v];
1569 /* Identify a variant from the strings the chess servers use or the
1570 PGN Variant tag names we use. */
1577 VariantClass v = VariantNormal;
1578 int i, found = FALSE;
1583 /* [HGM] skip over optional board-size prefixes */
1584 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1585 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1586 while( *e++ != '_');
1589 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1593 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1594 if (StrCaseStr(e, variantNames[i])) {
1595 v = (VariantClass) i;
1602 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1603 || StrCaseStr(e, "wild/fr")
1604 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1605 v = VariantFischeRandom;
1606 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1607 (i = 1, p = StrCaseStr(e, "w"))) {
1609 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1616 case 0: /* FICS only, actually */
1618 /* Castling legal even if K starts on d-file */
1619 v = VariantWildCastle;
1624 /* Castling illegal even if K & R happen to start in
1625 normal positions. */
1626 v = VariantNoCastle;
1639 /* Castling legal iff K & R start in normal positions */
1645 /* Special wilds for position setup; unclear what to do here */
1646 v = VariantLoadable;
1649 /* Bizarre ICC game */
1650 v = VariantTwoKings;
1653 v = VariantKriegspiel;
1659 v = VariantFischeRandom;
1662 v = VariantCrazyhouse;
1665 v = VariantBughouse;
1671 /* Not quite the same as FICS suicide! */
1672 v = VariantGiveaway;
1678 v = VariantShatranj;
1681 /* Temporary names for future ICC types. The name *will* change in
1682 the next xboard/WinBoard release after ICC defines it. */
1720 v = VariantCapablanca;
1723 v = VariantKnightmate;
1729 v = VariantCylinder;
1735 v = VariantCapaRandom;
1738 v = VariantBerolina;
1750 /* Found "wild" or "w" in the string but no number;
1751 must assume it's normal chess. */
1755 sprintf(buf, _("Unknown wild type %d"), wnum);
1756 DisplayError(buf, 0);
1762 if (appData.debugMode) {
1763 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1764 e, wnum, VariantName(v));
1769 static int leftover_start = 0, leftover_len = 0;
1770 char star_match[STAR_MATCH_N][MSG_SIZ];
1772 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1773 advance *index beyond it, and set leftover_start to the new value of
1774 *index; else return FALSE. If pattern contains the character '*', it
1775 matches any sequence of characters not containing '\r', '\n', or the
1776 character following the '*' (if any), and the matched sequence(s) are
1777 copied into star_match.
1780 looking_at(buf, index, pattern)
1785 char *bufp = &buf[*index], *patternp = pattern;
1787 char *matchp = star_match[0];
1790 if (*patternp == NULLCHAR) {
1791 *index = leftover_start = bufp - buf;
1795 if (*bufp == NULLCHAR) return FALSE;
1796 if (*patternp == '*') {
1797 if (*bufp == *(patternp + 1)) {
1799 matchp = star_match[++star_count];
1803 } else if (*bufp == '\n' || *bufp == '\r') {
1805 if (*patternp == NULLCHAR)
1810 *matchp++ = *bufp++;
1814 if (*patternp != *bufp) return FALSE;
1821 SendToPlayer(data, length)
1825 int error, outCount;
1826 outCount = OutputToProcess(NoProc, data, length, &error);
1827 if (outCount < length) {
1828 DisplayFatalError(_("Error writing to display"), error, 1);
1833 PackHolding(packed, holding)
1845 switch (runlength) {
1856 sprintf(q, "%d", runlength);
1868 /* Telnet protocol requests from the front end */
1870 TelnetRequest(ddww, option)
1871 unsigned char ddww, option;
1873 unsigned char msg[3];
1874 int outCount, outError;
1876 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1878 if (appData.debugMode) {
1879 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1895 sprintf(buf1, "%d", ddww);
1904 sprintf(buf2, "%d", option);
1907 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1912 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1914 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1921 if (!appData.icsActive) return;
1922 TelnetRequest(TN_DO, TN_ECHO);
1928 if (!appData.icsActive) return;
1929 TelnetRequest(TN_DONT, TN_ECHO);
1933 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1935 /* put the holdings sent to us by the server on the board holdings area */
1936 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1940 if(gameInfo.holdingsWidth < 2) return;
1941 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1942 return; // prevent overwriting by pre-board holdings
1944 if( (int)lowestPiece >= BlackPawn ) {
1947 holdingsStartRow = BOARD_HEIGHT-1;
1950 holdingsColumn = BOARD_WIDTH-1;
1951 countsColumn = BOARD_WIDTH-2;
1952 holdingsStartRow = 0;
1956 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1957 board[i][holdingsColumn] = EmptySquare;
1958 board[i][countsColumn] = (ChessSquare) 0;
1960 while( (p=*holdings++) != NULLCHAR ) {
1961 piece = CharToPiece( ToUpper(p) );
1962 if(piece == EmptySquare) continue;
1963 /*j = (int) piece - (int) WhitePawn;*/
1964 j = PieceToNumber(piece);
1965 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1966 if(j < 0) continue; /* should not happen */
1967 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1968 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1969 board[holdingsStartRow+j*direction][countsColumn]++;
1975 VariantSwitch(Board board, VariantClass newVariant)
1977 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1980 startedFromPositionFile = FALSE;
1981 if(gameInfo.variant == newVariant) return;
1983 /* [HGM] This routine is called each time an assignment is made to
1984 * gameInfo.variant during a game, to make sure the board sizes
1985 * are set to match the new variant. If that means adding or deleting
1986 * holdings, we shift the playing board accordingly
1987 * This kludge is needed because in ICS observe mode, we get boards
1988 * of an ongoing game without knowing the variant, and learn about the
1989 * latter only later. This can be because of the move list we requested,
1990 * in which case the game history is refilled from the beginning anyway,
1991 * but also when receiving holdings of a crazyhouse game. In the latter
1992 * case we want to add those holdings to the already received position.
1996 if (appData.debugMode) {
1997 fprintf(debugFP, "Switch board from %s to %s\n",
1998 VariantName(gameInfo.variant), VariantName(newVariant));
1999 setbuf(debugFP, NULL);
2001 shuffleOpenings = 0; /* [HGM] shuffle */
2002 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2006 newWidth = 9; newHeight = 9;
2007 gameInfo.holdingsSize = 7;
2008 case VariantBughouse:
2009 case VariantCrazyhouse:
2010 newHoldingsWidth = 2; break;
2014 newHoldingsWidth = 2;
2015 gameInfo.holdingsSize = 8;
2018 case VariantCapablanca:
2019 case VariantCapaRandom:
2022 newHoldingsWidth = gameInfo.holdingsSize = 0;
2025 if(newWidth != gameInfo.boardWidth ||
2026 newHeight != gameInfo.boardHeight ||
2027 newHoldingsWidth != gameInfo.holdingsWidth ) {
2029 /* shift position to new playing area, if needed */
2030 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2031 for(i=0; i<BOARD_HEIGHT; i++)
2032 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2033 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2035 for(i=0; i<newHeight; i++) {
2036 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2037 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2039 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2040 for(i=0; i<BOARD_HEIGHT; i++)
2041 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2042 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2045 gameInfo.boardWidth = newWidth;
2046 gameInfo.boardHeight = newHeight;
2047 gameInfo.holdingsWidth = newHoldingsWidth;
2048 gameInfo.variant = newVariant;
2049 InitDrawingSizes(-2, 0);
2050 } else gameInfo.variant = newVariant;
2051 CopyBoard(oldBoard, board); // remember correctly formatted board
2052 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2053 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2056 static int loggedOn = FALSE;
2058 /*-- Game start info cache: --*/
2060 char gs_kind[MSG_SIZ];
2061 static char player1Name[128] = "";
2062 static char player2Name[128] = "";
2063 static char cont_seq[] = "\n\\ ";
2064 static int player1Rating = -1;
2065 static int player2Rating = -1;
2066 /*----------------------------*/
2068 ColorClass curColor = ColorNormal;
2069 int suppressKibitz = 0;
2072 Boolean soughtPending = FALSE;
2073 Boolean seekGraphUp;
2074 #define MAX_SEEK_ADS 200
2076 char *seekAdList[MAX_SEEK_ADS];
2077 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2078 float tcList[MAX_SEEK_ADS];
2079 char colorList[MAX_SEEK_ADS];
2080 int nrOfSeekAds = 0;
2081 int minRating = 1010, maxRating = 2800;
2082 int hMargin = 10, vMargin = 20, h, w;
2083 extern int squareSize, lineGap;
2088 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2089 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2090 if(r < minRating+100 && r >=0 ) r = minRating+100;
2091 if(r > maxRating) r = maxRating;
2092 if(tc < 1.) tc = 1.;
2093 if(tc > 95.) tc = 95.;
2094 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2095 y = ((double)r - minRating)/(maxRating - minRating)
2096 * (h-vMargin-squareSize/8-1) + vMargin;
2097 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2098 if(strstr(seekAdList[i], " u ")) color = 1;
2099 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2100 !strstr(seekAdList[i], "bullet") &&
2101 !strstr(seekAdList[i], "blitz") &&
2102 !strstr(seekAdList[i], "standard") ) color = 2;
2103 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2104 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2108 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2110 char buf[MSG_SIZ], *ext = "";
2111 VariantClass v = StringToVariant(type);
2112 if(strstr(type, "wild")) {
2113 ext = type + 4; // append wild number
2114 if(v == VariantFischeRandom) type = "chess960"; else
2115 if(v == VariantLoadable) type = "setup"; else
2116 type = VariantName(v);
2118 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2119 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2120 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2121 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2122 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2123 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2124 seekNrList[nrOfSeekAds] = nr;
2125 zList[nrOfSeekAds] = 0;
2126 seekAdList[nrOfSeekAds++] = StrSave(buf);
2127 if(plot) PlotSeekAd(nrOfSeekAds-1);
2134 int x = xList[i], y = yList[i], d=squareSize/4, k;
2135 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2136 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2137 // now replot every dot that overlapped
2138 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2139 int xx = xList[k], yy = yList[k];
2140 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2141 DrawSeekDot(xx, yy, colorList[k]);
2146 RemoveSeekAd(int nr)
2149 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2151 if(seekAdList[i]) free(seekAdList[i]);
2152 seekAdList[i] = seekAdList[--nrOfSeekAds];
2153 seekNrList[i] = seekNrList[nrOfSeekAds];
2154 ratingList[i] = ratingList[nrOfSeekAds];
2155 colorList[i] = colorList[nrOfSeekAds];
2156 tcList[i] = tcList[nrOfSeekAds];
2157 xList[i] = xList[nrOfSeekAds];
2158 yList[i] = yList[nrOfSeekAds];
2159 zList[i] = zList[nrOfSeekAds];
2160 seekAdList[nrOfSeekAds] = NULL;
2166 MatchSoughtLine(char *line)
2168 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2169 int nr, base, inc, u=0; char dummy;
2171 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2172 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2174 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2175 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2176 // match: compact and save the line
2177 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2186 if(!seekGraphUp) return FALSE;
2188 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2189 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2191 DrawSeekBackground(0, 0, w, h);
2192 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2193 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2194 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2195 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2197 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2200 sprintf(buf, "%d", i);
2201 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2204 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2205 for(i=1; i<100; i+=(i<10?1:5)) {
2206 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2207 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2208 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2210 sprintf(buf, "%d", i);
2211 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2214 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2218 int SeekGraphClick(ClickType click, int x, int y, int moving)
2220 static int lastDown = 0, displayed = 0, lastSecond;
2221 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2222 if(click == Release || moving) return FALSE;
2224 soughtPending = TRUE;
2225 SendToICS(ics_prefix);
2226 SendToICS("sought\n"); // should this be "sought all"?
2227 } else { // issue challenge based on clicked ad
2228 int dist = 10000; int i, closest = 0, second = 0;
2229 for(i=0; i<nrOfSeekAds; i++) {
2230 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2231 if(d < dist) { dist = d; closest = i; }
2232 second += (d - zList[i] < 120); // count in-range ads
2233 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2237 second = (second > 1);
2238 if(displayed != closest || second != lastSecond) {
2239 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2240 lastSecond = second; displayed = closest;
2242 if(click == Press) {
2243 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2246 } // on press 'hit', only show info
2247 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2248 sprintf(buf, "play %d\n", seekNrList[closest]);
2249 SendToICS(ics_prefix);
2251 return TRUE; // let incoming board of started game pop down the graph
2252 } else if(click == Release) { // release 'miss' is ignored
2253 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2254 if(moving == 2) { // right up-click
2255 nrOfSeekAds = 0; // refresh graph
2256 soughtPending = TRUE;
2257 SendToICS(ics_prefix);
2258 SendToICS("sought\n"); // should this be "sought all"?
2261 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2262 // press miss or release hit 'pop down' seek graph
2263 seekGraphUp = FALSE;
2264 DrawPosition(TRUE, NULL);
2270 read_from_ics(isr, closure, data, count, error)
2277 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2278 #define STARTED_NONE 0
2279 #define STARTED_MOVES 1
2280 #define STARTED_BOARD 2
2281 #define STARTED_OBSERVE 3
2282 #define STARTED_HOLDINGS 4
2283 #define STARTED_CHATTER 5
2284 #define STARTED_COMMENT 6
2285 #define STARTED_MOVES_NOHIDE 7
2287 static int started = STARTED_NONE;
2288 static char parse[20000];
2289 static int parse_pos = 0;
2290 static char buf[BUF_SIZE + 1];
2291 static int firstTime = TRUE, intfSet = FALSE;
2292 static ColorClass prevColor = ColorNormal;
2293 static int savingComment = FALSE;
2294 static int cmatch = 0; // continuation sequence match
2301 int backup; /* [DM] For zippy color lines */
2303 char talker[MSG_SIZ]; // [HGM] chat
2306 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2308 if (appData.debugMode) {
2310 fprintf(debugFP, "<ICS: ");
2311 show_bytes(debugFP, data, count);
2312 fprintf(debugFP, "\n");
2316 if (appData.debugMode) { int f = forwardMostMove;
2317 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2318 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2319 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2322 /* If last read ended with a partial line that we couldn't parse,
2323 prepend it to the new read and try again. */
2324 if (leftover_len > 0) {
2325 for (i=0; i<leftover_len; i++)
2326 buf[i] = buf[leftover_start + i];
2329 /* copy new characters into the buffer */
2330 bp = buf + leftover_len;
2331 buf_len=leftover_len;
2332 for (i=0; i<count; i++)
2335 if (data[i] == '\r')
2338 // join lines split by ICS?
2339 if (!appData.noJoin)
2342 Joining just consists of finding matches against the
2343 continuation sequence, and discarding that sequence
2344 if found instead of copying it. So, until a match
2345 fails, there's nothing to do since it might be the
2346 complete sequence, and thus, something we don't want
2349 if (data[i] == cont_seq[cmatch])
2352 if (cmatch == strlen(cont_seq))
2354 cmatch = 0; // complete match. just reset the counter
2357 it's possible for the ICS to not include the space
2358 at the end of the last word, making our [correct]
2359 join operation fuse two separate words. the server
2360 does this when the space occurs at the width setting.
2362 if (!buf_len || buf[buf_len-1] != ' ')
2373 match failed, so we have to copy what matched before
2374 falling through and copying this character. In reality,
2375 this will only ever be just the newline character, but
2376 it doesn't hurt to be precise.
2378 strncpy(bp, cont_seq, cmatch);
2390 buf[buf_len] = NULLCHAR;
2391 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2396 while (i < buf_len) {
2397 /* Deal with part of the TELNET option negotiation
2398 protocol. We refuse to do anything beyond the
2399 defaults, except that we allow the WILL ECHO option,
2400 which ICS uses to turn off password echoing when we are
2401 directly connected to it. We reject this option
2402 if localLineEditing mode is on (always on in xboard)
2403 and we are talking to port 23, which might be a real
2404 telnet server that will try to keep WILL ECHO on permanently.
2406 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2407 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2408 unsigned char option;
2410 switch ((unsigned char) buf[++i]) {
2412 if (appData.debugMode)
2413 fprintf(debugFP, "\n<WILL ");
2414 switch (option = (unsigned char) buf[++i]) {
2416 if (appData.debugMode)
2417 fprintf(debugFP, "ECHO ");
2418 /* Reply only if this is a change, according
2419 to the protocol rules. */
2420 if (remoteEchoOption) break;
2421 if (appData.localLineEditing &&
2422 atoi(appData.icsPort) == TN_PORT) {
2423 TelnetRequest(TN_DONT, TN_ECHO);
2426 TelnetRequest(TN_DO, TN_ECHO);
2427 remoteEchoOption = TRUE;
2431 if (appData.debugMode)
2432 fprintf(debugFP, "%d ", option);
2433 /* Whatever this is, we don't want it. */
2434 TelnetRequest(TN_DONT, option);
2439 if (appData.debugMode)
2440 fprintf(debugFP, "\n<WONT ");
2441 switch (option = (unsigned char) buf[++i]) {
2443 if (appData.debugMode)
2444 fprintf(debugFP, "ECHO ");
2445 /* Reply only if this is a change, according
2446 to the protocol rules. */
2447 if (!remoteEchoOption) break;
2449 TelnetRequest(TN_DONT, TN_ECHO);
2450 remoteEchoOption = FALSE;
2453 if (appData.debugMode)
2454 fprintf(debugFP, "%d ", (unsigned char) option);
2455 /* Whatever this is, it must already be turned
2456 off, because we never agree to turn on
2457 anything non-default, so according to the
2458 protocol rules, we don't reply. */
2463 if (appData.debugMode)
2464 fprintf(debugFP, "\n<DO ");
2465 switch (option = (unsigned char) buf[++i]) {
2467 /* Whatever this is, we refuse to do it. */
2468 if (appData.debugMode)
2469 fprintf(debugFP, "%d ", option);
2470 TelnetRequest(TN_WONT, option);
2475 if (appData.debugMode)
2476 fprintf(debugFP, "\n<DONT ");
2477 switch (option = (unsigned char) buf[++i]) {
2479 if (appData.debugMode)
2480 fprintf(debugFP, "%d ", option);
2481 /* Whatever this is, we are already not doing
2482 it, because we never agree to do anything
2483 non-default, so according to the protocol
2484 rules, we don't reply. */
2489 if (appData.debugMode)
2490 fprintf(debugFP, "\n<IAC ");
2491 /* Doubled IAC; pass it through */
2495 if (appData.debugMode)
2496 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2497 /* Drop all other telnet commands on the floor */
2500 if (oldi > next_out)
2501 SendToPlayer(&buf[next_out], oldi - next_out);
2507 /* OK, this at least will *usually* work */
2508 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2512 if (loggedOn && !intfSet) {
2513 if (ics_type == ICS_ICC) {
2515 "/set-quietly interface %s\n/set-quietly style 12\n",
2517 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2518 strcat(str, "/set-2 51 1\n/set seek 1\n");
2519 } else if (ics_type == ICS_CHESSNET) {
2520 sprintf(str, "/style 12\n");
2522 strcpy(str, "alias $ @\n$set interface ");
2523 strcat(str, programVersion);
2524 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2525 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2526 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2528 strcat(str, "$iset nohighlight 1\n");
2530 strcat(str, "$iset lock 1\n$style 12\n");
2533 NotifyFrontendLogin();
2537 if (started == STARTED_COMMENT) {
2538 /* Accumulate characters in comment */
2539 parse[parse_pos++] = buf[i];
2540 if (buf[i] == '\n') {
2541 parse[parse_pos] = NULLCHAR;
2542 if(chattingPartner>=0) {
2544 sprintf(mess, "%s%s", talker, parse);
2545 OutputChatMessage(chattingPartner, mess);
2546 chattingPartner = -1;
2547 next_out = i+1; // [HGM] suppress printing in ICS window
2549 if(!suppressKibitz) // [HGM] kibitz
2550 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2551 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2552 int nrDigit = 0, nrAlph = 0, j;
2553 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2554 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2555 parse[parse_pos] = NULLCHAR;
2556 // try to be smart: if it does not look like search info, it should go to
2557 // ICS interaction window after all, not to engine-output window.
2558 for(j=0; j<parse_pos; j++) { // count letters and digits
2559 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2560 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2561 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2563 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2564 int depth=0; float score;
2565 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2566 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2567 pvInfoList[forwardMostMove-1].depth = depth;
2568 pvInfoList[forwardMostMove-1].score = 100*score;
2570 OutputKibitz(suppressKibitz, parse);
2573 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2574 SendToPlayer(tmp, strlen(tmp));
2576 next_out = i+1; // [HGM] suppress printing in ICS window
2578 started = STARTED_NONE;
2580 /* Don't match patterns against characters in comment */
2585 if (started == STARTED_CHATTER) {
2586 if (buf[i] != '\n') {
2587 /* Don't match patterns against characters in chatter */
2591 started = STARTED_NONE;
2592 if(suppressKibitz) next_out = i+1;
2595 /* Kludge to deal with rcmd protocol */
2596 if (firstTime && looking_at(buf, &i, "\001*")) {
2597 DisplayFatalError(&buf[1], 0, 1);
2603 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2606 if (appData.debugMode)
2607 fprintf(debugFP, "ics_type %d\n", ics_type);
2610 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2611 ics_type = ICS_FICS;
2613 if (appData.debugMode)
2614 fprintf(debugFP, "ics_type %d\n", ics_type);
2617 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2618 ics_type = ICS_CHESSNET;
2620 if (appData.debugMode)
2621 fprintf(debugFP, "ics_type %d\n", ics_type);
2626 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2627 looking_at(buf, &i, "Logging you in as \"*\"") ||
2628 looking_at(buf, &i, "will be \"*\""))) {
2629 strcpy(ics_handle, star_match[0]);
2633 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2635 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2636 DisplayIcsInteractionTitle(buf);
2637 have_set_title = TRUE;
2640 /* skip finger notes */
2641 if (started == STARTED_NONE &&
2642 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2643 (buf[i] == '1' && buf[i+1] == '0')) &&
2644 buf[i+2] == ':' && buf[i+3] == ' ') {
2645 started = STARTED_CHATTER;
2651 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2652 if(appData.seekGraph) {
2653 if(soughtPending && MatchSoughtLine(buf+i)) {
2654 i = strstr(buf+i, "rated") - buf;
2655 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2656 next_out = leftover_start = i;
2657 started = STARTED_CHATTER;
2658 suppressKibitz = TRUE;
2661 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2662 && looking_at(buf, &i, "* ads displayed")) {
2663 soughtPending = FALSE;
2668 if(appData.autoRefresh) {
2669 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2670 int s = (ics_type == ICS_ICC); // ICC format differs
2672 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2673 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2674 looking_at(buf, &i, "*% "); // eat prompt
2675 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2676 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2677 next_out = i; // suppress
2680 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2681 char *p = star_match[0];
2683 if(seekGraphUp) RemoveSeekAd(atoi(p));
2684 while(*p && *p++ != ' '); // next
2686 looking_at(buf, &i, "*% "); // eat prompt
2687 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2694 /* skip formula vars */
2695 if (started == STARTED_NONE &&
2696 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2697 started = STARTED_CHATTER;
2702 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2703 if (appData.autoKibitz && started == STARTED_NONE &&
2704 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2705 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2706 if(looking_at(buf, &i, "* kibitzes: ") &&
2707 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2708 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2709 suppressKibitz = TRUE;
2710 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2712 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2713 && (gameMode == IcsPlayingWhite)) ||
2714 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2715 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2716 started = STARTED_CHATTER; // own kibitz we simply discard
2718 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2719 parse_pos = 0; parse[0] = NULLCHAR;
2720 savingComment = TRUE;
2721 suppressKibitz = gameMode != IcsObserving ? 2 :
2722 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2726 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2727 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2728 && atoi(star_match[0])) {
2729 // suppress the acknowledgements of our own autoKibitz
2731 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2732 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2733 SendToPlayer(star_match[0], strlen(star_match[0]));
2734 if(looking_at(buf, &i, "*% ")) // eat prompt
2735 suppressKibitz = FALSE;
2739 } // [HGM] kibitz: end of patch
2741 // [HGM] chat: intercept tells by users for which we have an open chat window
2743 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2744 looking_at(buf, &i, "* whispers:") ||
2745 looking_at(buf, &i, "* shouts:") ||
2746 looking_at(buf, &i, "* c-shouts:") ||
2747 looking_at(buf, &i, "--> * ") ||
2748 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2749 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2750 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2751 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2753 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2754 chattingPartner = -1;
2756 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2757 for(p=0; p<MAX_CHAT; p++) {
2758 if(channel == atoi(chatPartner[p])) {
2759 talker[0] = '['; strcat(talker, "] ");
2760 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2761 chattingPartner = p; break;
2764 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2765 for(p=0; p<MAX_CHAT; p++) {
2766 if(!strcmp("whispers", chatPartner[p])) {
2767 talker[0] = '['; strcat(talker, "] ");
2768 chattingPartner = p; break;
2771 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2772 for(p=0; p<MAX_CHAT; p++) {
2773 if(!strcmp("shouts", chatPartner[p])) {
2774 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2775 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2776 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2777 chattingPartner = p; break;
2780 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2781 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2782 talker[0] = 0; Colorize(ColorTell, FALSE);
2783 chattingPartner = p; break;
2785 if(chattingPartner<0) i = oldi; else {
2786 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2787 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2788 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2789 started = STARTED_COMMENT;
2790 parse_pos = 0; parse[0] = NULLCHAR;
2791 savingComment = 3 + chattingPartner; // counts as TRUE
2792 suppressKibitz = TRUE;
2795 } // [HGM] chat: end of patch
2797 if (appData.zippyTalk || appData.zippyPlay) {
2798 /* [DM] Backup address for color zippy lines */
2802 if (loggedOn == TRUE)
2803 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2804 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2806 if (ZippyControl(buf, &i) ||
2807 ZippyConverse(buf, &i) ||
2808 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2810 if (!appData.colorize) continue;
2814 } // [DM] 'else { ' deleted
2816 /* Regular tells and says */
2817 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2818 looking_at(buf, &i, "* (your partner) tells you: ") ||
2819 looking_at(buf, &i, "* says: ") ||
2820 /* Don't color "message" or "messages" output */
2821 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2822 looking_at(buf, &i, "*. * at *:*: ") ||
2823 looking_at(buf, &i, "--* (*:*): ") ||
2824 /* Message notifications (same color as tells) */
2825 looking_at(buf, &i, "* has left a message ") ||
2826 looking_at(buf, &i, "* just sent you a message:\n") ||
2827 /* Whispers and kibitzes */
2828 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2829 looking_at(buf, &i, "* kibitzes: ") ||
2831 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2833 if (tkind == 1 && strchr(star_match[0], ':')) {
2834 /* Avoid "tells you:" spoofs in channels */
2837 if (star_match[0][0] == NULLCHAR ||
2838 strchr(star_match[0], ' ') ||
2839 (tkind == 3 && strchr(star_match[1], ' '))) {
2840 /* Reject bogus matches */
2843 if (appData.colorize) {
2844 if (oldi > next_out) {
2845 SendToPlayer(&buf[next_out], oldi - next_out);
2850 Colorize(ColorTell, FALSE);
2851 curColor = ColorTell;
2854 Colorize(ColorKibitz, FALSE);
2855 curColor = ColorKibitz;
2858 p = strrchr(star_match[1], '(');
2865 Colorize(ColorChannel1, FALSE);
2866 curColor = ColorChannel1;
2868 Colorize(ColorChannel, FALSE);
2869 curColor = ColorChannel;
2873 curColor = ColorNormal;
2877 if (started == STARTED_NONE && appData.autoComment &&
2878 (gameMode == IcsObserving ||
2879 gameMode == IcsPlayingWhite ||
2880 gameMode == IcsPlayingBlack)) {
2881 parse_pos = i - oldi;
2882 memcpy(parse, &buf[oldi], parse_pos);
2883 parse[parse_pos] = NULLCHAR;
2884 started = STARTED_COMMENT;
2885 savingComment = TRUE;
2887 started = STARTED_CHATTER;
2888 savingComment = FALSE;
2895 if (looking_at(buf, &i, "* s-shouts: ") ||
2896 looking_at(buf, &i, "* c-shouts: ")) {
2897 if (appData.colorize) {
2898 if (oldi > next_out) {
2899 SendToPlayer(&buf[next_out], oldi - next_out);
2902 Colorize(ColorSShout, FALSE);
2903 curColor = ColorSShout;
2906 started = STARTED_CHATTER;
2910 if (looking_at(buf, &i, "--->")) {
2915 if (looking_at(buf, &i, "* shouts: ") ||
2916 looking_at(buf, &i, "--> ")) {
2917 if (appData.colorize) {
2918 if (oldi > next_out) {
2919 SendToPlayer(&buf[next_out], oldi - next_out);
2922 Colorize(ColorShout, FALSE);
2923 curColor = ColorShout;
2926 started = STARTED_CHATTER;
2930 if (looking_at( buf, &i, "Challenge:")) {
2931 if (appData.colorize) {
2932 if (oldi > next_out) {
2933 SendToPlayer(&buf[next_out], oldi - next_out);
2936 Colorize(ColorChallenge, FALSE);
2937 curColor = ColorChallenge;
2943 if (looking_at(buf, &i, "* offers you") ||
2944 looking_at(buf, &i, "* offers to be") ||
2945 looking_at(buf, &i, "* would like to") ||
2946 looking_at(buf, &i, "* requests to") ||
2947 looking_at(buf, &i, "Your opponent offers") ||
2948 looking_at(buf, &i, "Your opponent requests")) {
2950 if (appData.colorize) {
2951 if (oldi > next_out) {
2952 SendToPlayer(&buf[next_out], oldi - next_out);
2955 Colorize(ColorRequest, FALSE);
2956 curColor = ColorRequest;
2961 if (looking_at(buf, &i, "* (*) seeking")) {
2962 if (appData.colorize) {
2963 if (oldi > next_out) {
2964 SendToPlayer(&buf[next_out], oldi - next_out);
2967 Colorize(ColorSeek, FALSE);
2968 curColor = ColorSeek;
2973 if (looking_at(buf, &i, "\\ ")) {
2974 if (prevColor != ColorNormal) {
2975 if (oldi > next_out) {
2976 SendToPlayer(&buf[next_out], oldi - next_out);
2979 Colorize(prevColor, TRUE);
2980 curColor = prevColor;
2982 if (savingComment) {
2983 parse_pos = i - oldi;
2984 memcpy(parse, &buf[oldi], parse_pos);
2985 parse[parse_pos] = NULLCHAR;
2986 started = STARTED_COMMENT;
2987 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2988 chattingPartner = savingComment - 3; // kludge to remember the box
2990 started = STARTED_CHATTER;
2995 if (looking_at(buf, &i, "Black Strength :") ||
2996 looking_at(buf, &i, "<<< style 10 board >>>") ||
2997 looking_at(buf, &i, "<10>") ||
2998 looking_at(buf, &i, "#@#")) {
2999 /* Wrong board style */
3001 SendToICS(ics_prefix);
3002 SendToICS("set style 12\n");
3003 SendToICS(ics_prefix);
3004 SendToICS("refresh\n");
3008 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3010 have_sent_ICS_logon = 1;
3014 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3015 (looking_at(buf, &i, "\n<12> ") ||
3016 looking_at(buf, &i, "<12> "))) {
3018 if (oldi > next_out) {
3019 SendToPlayer(&buf[next_out], oldi - next_out);
3022 started = STARTED_BOARD;
3027 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3028 looking_at(buf, &i, "<b1> ")) {
3029 if (oldi > next_out) {
3030 SendToPlayer(&buf[next_out], oldi - next_out);
3033 started = STARTED_HOLDINGS;
3038 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3040 /* Header for a move list -- first line */
3042 switch (ics_getting_history) {
3046 case BeginningOfGame:
3047 /* User typed "moves" or "oldmoves" while we
3048 were idle. Pretend we asked for these
3049 moves and soak them up so user can step
3050 through them and/or save them.
3053 gameMode = IcsObserving;
3056 ics_getting_history = H_GOT_UNREQ_HEADER;
3058 case EditGame: /*?*/
3059 case EditPosition: /*?*/
3060 /* Should above feature work in these modes too? */
3061 /* For now it doesn't */
3062 ics_getting_history = H_GOT_UNWANTED_HEADER;
3065 ics_getting_history = H_GOT_UNWANTED_HEADER;
3070 /* Is this the right one? */
3071 if (gameInfo.white && gameInfo.black &&
3072 strcmp(gameInfo.white, star_match[0]) == 0 &&
3073 strcmp(gameInfo.black, star_match[2]) == 0) {
3075 ics_getting_history = H_GOT_REQ_HEADER;
3078 case H_GOT_REQ_HEADER:
3079 case H_GOT_UNREQ_HEADER:
3080 case H_GOT_UNWANTED_HEADER:
3081 case H_GETTING_MOVES:
3082 /* Should not happen */
3083 DisplayError(_("Error gathering move list: two headers"), 0);
3084 ics_getting_history = H_FALSE;
3088 /* Save player ratings into gameInfo if needed */
3089 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3090 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3091 (gameInfo.whiteRating == -1 ||
3092 gameInfo.blackRating == -1)) {
3094 gameInfo.whiteRating = string_to_rating(star_match[1]);
3095 gameInfo.blackRating = string_to_rating(star_match[3]);
3096 if (appData.debugMode)
3097 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3098 gameInfo.whiteRating, gameInfo.blackRating);
3103 if (looking_at(buf, &i,
3104 "* * match, initial time: * minute*, increment: * second")) {
3105 /* Header for a move list -- second line */
3106 /* Initial board will follow if this is a wild game */
3107 if (gameInfo.event != NULL) free(gameInfo.event);
3108 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3109 gameInfo.event = StrSave(str);
3110 /* [HGM] we switched variant. Translate boards if needed. */
3111 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3115 if (looking_at(buf, &i, "Move ")) {
3116 /* Beginning of a move list */
3117 switch (ics_getting_history) {
3119 /* Normally should not happen */
3120 /* Maybe user hit reset while we were parsing */
3123 /* Happens if we are ignoring a move list that is not
3124 * the one we just requested. Common if the user
3125 * tries to observe two games without turning off
3128 case H_GETTING_MOVES:
3129 /* Should not happen */
3130 DisplayError(_("Error gathering move list: nested"), 0);
3131 ics_getting_history = H_FALSE;
3133 case H_GOT_REQ_HEADER:
3134 ics_getting_history = H_GETTING_MOVES;
3135 started = STARTED_MOVES;
3137 if (oldi > next_out) {
3138 SendToPlayer(&buf[next_out], oldi - next_out);
3141 case H_GOT_UNREQ_HEADER:
3142 ics_getting_history = H_GETTING_MOVES;
3143 started = STARTED_MOVES_NOHIDE;
3146 case H_GOT_UNWANTED_HEADER:
3147 ics_getting_history = H_FALSE;
3153 if (looking_at(buf, &i, "% ") ||
3154 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3155 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3156 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3157 soughtPending = FALSE;
3161 if(suppressKibitz) next_out = i;
3162 savingComment = FALSE;
3166 case STARTED_MOVES_NOHIDE:
3167 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3168 parse[parse_pos + i - oldi] = NULLCHAR;
3169 ParseGameHistory(parse);
3171 if (appData.zippyPlay && first.initDone) {
3172 FeedMovesToProgram(&first, forwardMostMove);
3173 if (gameMode == IcsPlayingWhite) {
3174 if (WhiteOnMove(forwardMostMove)) {
3175 if (first.sendTime) {
3176 if (first.useColors) {
3177 SendToProgram("black\n", &first);
3179 SendTimeRemaining(&first, TRUE);
3181 if (first.useColors) {
3182 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3184 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3185 first.maybeThinking = TRUE;
3187 if (first.usePlayother) {
3188 if (first.sendTime) {
3189 SendTimeRemaining(&first, TRUE);
3191 SendToProgram("playother\n", &first);
3197 } else if (gameMode == IcsPlayingBlack) {
3198 if (!WhiteOnMove(forwardMostMove)) {
3199 if (first.sendTime) {
3200 if (first.useColors) {
3201 SendToProgram("white\n", &first);
3203 SendTimeRemaining(&first, FALSE);
3205 if (first.useColors) {
3206 SendToProgram("black\n", &first);
3208 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3209 first.maybeThinking = TRUE;
3211 if (first.usePlayother) {
3212 if (first.sendTime) {
3213 SendTimeRemaining(&first, FALSE);
3215 SendToProgram("playother\n", &first);
3224 if (gameMode == IcsObserving && ics_gamenum == -1) {
3225 /* Moves came from oldmoves or moves command
3226 while we weren't doing anything else.
3228 currentMove = forwardMostMove;
3229 ClearHighlights();/*!!could figure this out*/
3230 flipView = appData.flipView;
3231 DrawPosition(TRUE, boards[currentMove]);
3232 DisplayBothClocks();
3233 sprintf(str, "%s vs. %s",
3234 gameInfo.white, gameInfo.black);
3238 /* Moves were history of an active game */
3239 if (gameInfo.resultDetails != NULL) {
3240 free(gameInfo.resultDetails);
3241 gameInfo.resultDetails = NULL;
3244 HistorySet(parseList, backwardMostMove,
3245 forwardMostMove, currentMove-1);
3246 DisplayMove(currentMove - 1);
3247 if (started == STARTED_MOVES) next_out = i;
3248 started = STARTED_NONE;
3249 ics_getting_history = H_FALSE;
3252 case STARTED_OBSERVE:
3253 started = STARTED_NONE;
3254 SendToICS(ics_prefix);
3255 SendToICS("refresh\n");
3261 if(bookHit) { // [HGM] book: simulate book reply
3262 static char bookMove[MSG_SIZ]; // a bit generous?
3264 programStats.nodes = programStats.depth = programStats.time =
3265 programStats.score = programStats.got_only_move = 0;
3266 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3268 strcpy(bookMove, "move ");
3269 strcat(bookMove, bookHit);
3270 HandleMachineMove(bookMove, &first);
3275 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3276 started == STARTED_HOLDINGS ||
3277 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3278 /* Accumulate characters in move list or board */
3279 parse[parse_pos++] = buf[i];
3282 /* Start of game messages. Mostly we detect start of game
3283 when the first board image arrives. On some versions
3284 of the ICS, though, we need to do a "refresh" after starting
3285 to observe in order to get the current board right away. */
3286 if (looking_at(buf, &i, "Adding game * to observation list")) {
3287 started = STARTED_OBSERVE;
3291 /* Handle auto-observe */
3292 if (appData.autoObserve &&
3293 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3294 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3296 /* Choose the player that was highlighted, if any. */
3297 if (star_match[0][0] == '\033' ||
3298 star_match[1][0] != '\033') {
3299 player = star_match[0];
3301 player = star_match[2];
3303 sprintf(str, "%sobserve %s\n",
3304 ics_prefix, StripHighlightAndTitle(player));
3307 /* Save ratings from notify string */
3308 strcpy(player1Name, star_match[0]);
3309 player1Rating = string_to_rating(star_match[1]);
3310 strcpy(player2Name, star_match[2]);
3311 player2Rating = string_to_rating(star_match[3]);
3313 if (appData.debugMode)
3315 "Ratings from 'Game notification:' %s %d, %s %d\n",
3316 player1Name, player1Rating,
3317 player2Name, player2Rating);
3322 /* Deal with automatic examine mode after a game,
3323 and with IcsObserving -> IcsExamining transition */
3324 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3325 looking_at(buf, &i, "has made you an examiner of game *")) {
3327 int gamenum = atoi(star_match[0]);
3328 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3329 gamenum == ics_gamenum) {
3330 /* We were already playing or observing this game;
3331 no need to refetch history */
3332 gameMode = IcsExamining;
3334 pauseExamForwardMostMove = forwardMostMove;
3335 } else if (currentMove < forwardMostMove) {
3336 ForwardInner(forwardMostMove);
3339 /* I don't think this case really can happen */
3340 SendToICS(ics_prefix);
3341 SendToICS("refresh\n");
3346 /* Error messages */
3347 // if (ics_user_moved) {
3348 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3349 if (looking_at(buf, &i, "Illegal move") ||
3350 looking_at(buf, &i, "Not a legal move") ||
3351 looking_at(buf, &i, "Your king is in check") ||
3352 looking_at(buf, &i, "It isn't your turn") ||
3353 looking_at(buf, &i, "It is not your move")) {
3355 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3356 currentMove = forwardMostMove-1;
3357 DisplayMove(currentMove - 1); /* before DMError */
3358 DrawPosition(FALSE, boards[currentMove]);
3359 SwitchClocks(forwardMostMove-1); // [HGM] race
3360 DisplayBothClocks();
3362 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3368 if (looking_at(buf, &i, "still have time") ||
3369 looking_at(buf, &i, "not out of time") ||
3370 looking_at(buf, &i, "either player is out of time") ||
3371 looking_at(buf, &i, "has timeseal; checking")) {
3372 /* We must have called his flag a little too soon */
3373 whiteFlag = blackFlag = FALSE;
3377 if (looking_at(buf, &i, "added * seconds to") ||
3378 looking_at(buf, &i, "seconds were added to")) {
3379 /* Update the clocks */
3380 SendToICS(ics_prefix);
3381 SendToICS("refresh\n");
3385 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3386 ics_clock_paused = TRUE;
3391 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3392 ics_clock_paused = FALSE;
3397 /* Grab player ratings from the Creating: message.
3398 Note we have to check for the special case when
3399 the ICS inserts things like [white] or [black]. */
3400 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3401 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3403 0 player 1 name (not necessarily white)
3405 2 empty, white, or black (IGNORED)
3406 3 player 2 name (not necessarily black)
3409 The names/ratings are sorted out when the game
3410 actually starts (below).
3412 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3413 player1Rating = string_to_rating(star_match[1]);
3414 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3415 player2Rating = string_to_rating(star_match[4]);
3417 if (appData.debugMode)
3419 "Ratings from 'Creating:' %s %d, %s %d\n",
3420 player1Name, player1Rating,
3421 player2Name, player2Rating);
3426 /* Improved generic start/end-of-game messages */
3427 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3428 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3429 /* If tkind == 0: */
3430 /* star_match[0] is the game number */
3431 /* [1] is the white player's name */
3432 /* [2] is the black player's name */
3433 /* For end-of-game: */
3434 /* [3] is the reason for the game end */
3435 /* [4] is a PGN end game-token, preceded by " " */
3436 /* For start-of-game: */
3437 /* [3] begins with "Creating" or "Continuing" */
3438 /* [4] is " *" or empty (don't care). */
3439 int gamenum = atoi(star_match[0]);
3440 char *whitename, *blackname, *why, *endtoken;
3441 ChessMove endtype = (ChessMove) 0;
3444 whitename = star_match[1];
3445 blackname = star_match[2];
3446 why = star_match[3];
3447 endtoken = star_match[4];
3449 whitename = star_match[1];
3450 blackname = star_match[3];
3451 why = star_match[5];
3452 endtoken = star_match[6];
3455 /* Game start messages */
3456 if (strncmp(why, "Creating ", 9) == 0 ||
3457 strncmp(why, "Continuing ", 11) == 0) {
3458 gs_gamenum = gamenum;
3459 strcpy(gs_kind, strchr(why, ' ') + 1);
3460 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3462 if (appData.zippyPlay) {
3463 ZippyGameStart(whitename, blackname);
3469 /* Game end messages */
3470 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3471 ics_gamenum != gamenum) {
3474 while (endtoken[0] == ' ') endtoken++;
3475 switch (endtoken[0]) {
3478 endtype = GameUnfinished;
3481 endtype = BlackWins;
3484 if (endtoken[1] == '/')
3485 endtype = GameIsDrawn;
3487 endtype = WhiteWins;
3490 GameEnds(endtype, why, GE_ICS);
3492 if (appData.zippyPlay && first.initDone) {
3493 ZippyGameEnd(endtype, why);
3494 if (first.pr == NULL) {
3495 /* Start the next process early so that we'll
3496 be ready for the next challenge */
3497 StartChessProgram(&first);
3499 /* Send "new" early, in case this command takes
3500 a long time to finish, so that we'll be ready
3501 for the next challenge. */
3502 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3509 if (looking_at(buf, &i, "Removing game * from observation") ||
3510 looking_at(buf, &i, "no longer observing game *") ||
3511 looking_at(buf, &i, "Game * (*) has no examiners")) {
3512 if (gameMode == IcsObserving &&
3513 atoi(star_match[0]) == ics_gamenum)
3515 /* icsEngineAnalyze */
3516 if (appData.icsEngineAnalyze) {
3523 ics_user_moved = FALSE;
3528 if (looking_at(buf, &i, "no longer examining game *")) {
3529 if (gameMode == IcsExamining &&
3530 atoi(star_match[0]) == ics_gamenum)
3534 ics_user_moved = FALSE;
3539 /* Advance leftover_start past any newlines we find,
3540 so only partial lines can get reparsed */
3541 if (looking_at(buf, &i, "\n")) {
3542 prevColor = curColor;
3543 if (curColor != ColorNormal) {
3544 if (oldi > next_out) {
3545 SendToPlayer(&buf[next_out], oldi - next_out);
3548 Colorize(ColorNormal, FALSE);
3549 curColor = ColorNormal;
3551 if (started == STARTED_BOARD) {
3552 started = STARTED_NONE;
3553 parse[parse_pos] = NULLCHAR;
3554 ParseBoard12(parse);
3557 /* Send premove here */
3558 if (appData.premove) {
3560 if (currentMove == 0 &&
3561 gameMode == IcsPlayingWhite &&
3562 appData.premoveWhite) {
3563 sprintf(str, "%s\n", appData.premoveWhiteText);
3564 if (appData.debugMode)
3565 fprintf(debugFP, "Sending premove:\n");
3567 } else if (currentMove == 1 &&
3568 gameMode == IcsPlayingBlack &&
3569 appData.premoveBlack) {
3570 sprintf(str, "%s\n", appData.premoveBlackText);
3571 if (appData.debugMode)
3572 fprintf(debugFP, "Sending premove:\n");
3574 } else if (gotPremove) {
3576 ClearPremoveHighlights();
3577 if (appData.debugMode)
3578 fprintf(debugFP, "Sending premove:\n");
3579 UserMoveEvent(premoveFromX, premoveFromY,
3580 premoveToX, premoveToY,
3585 /* Usually suppress following prompt */
3586 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3587 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3588 if (looking_at(buf, &i, "*% ")) {
3589 savingComment = FALSE;
3594 } else if (started == STARTED_HOLDINGS) {
3596 char new_piece[MSG_SIZ];
3597 started = STARTED_NONE;
3598 parse[parse_pos] = NULLCHAR;
3599 if (appData.debugMode)
3600 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3601 parse, currentMove);
3602 if (sscanf(parse, " game %d", &gamenum) == 1) {
3603 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3604 if (gameInfo.variant == VariantNormal) {
3605 /* [HGM] We seem to switch variant during a game!
3606 * Presumably no holdings were displayed, so we have
3607 * to move the position two files to the right to
3608 * create room for them!
3610 VariantClass newVariant;
3611 switch(gameInfo.boardWidth) { // base guess on board width
3612 case 9: newVariant = VariantShogi; break;
3613 case 10: newVariant = VariantGreat; break;
3614 default: newVariant = VariantCrazyhouse; break;
3616 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3617 /* Get a move list just to see the header, which
3618 will tell us whether this is really bug or zh */
3619 if (ics_getting_history == H_FALSE) {
3620 ics_getting_history = H_REQUESTED;
3621 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3625 new_piece[0] = NULLCHAR;
3626 sscanf(parse, "game %d white [%s black [%s <- %s",
3627 &gamenum, white_holding, black_holding,
3629 white_holding[strlen(white_holding)-1] = NULLCHAR;
3630 black_holding[strlen(black_holding)-1] = NULLCHAR;
3631 /* [HGM] copy holdings to board holdings area */
3632 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3633 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3634 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3636 if (appData.zippyPlay && first.initDone) {
3637 ZippyHoldings(white_holding, black_holding,
3641 if (tinyLayout || smallLayout) {
3642 char wh[16], bh[16];
3643 PackHolding(wh, white_holding);
3644 PackHolding(bh, black_holding);
3645 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3646 gameInfo.white, gameInfo.black);
3648 sprintf(str, "%s [%s] vs. %s [%s]",
3649 gameInfo.white, white_holding,
3650 gameInfo.black, black_holding);
3652 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3653 DrawPosition(FALSE, boards[currentMove]);
3655 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3656 sscanf(parse, "game %d white [%s black [%s <- %s",
3657 &gamenum, white_holding, black_holding,
3659 white_holding[strlen(white_holding)-1] = NULLCHAR;
3660 black_holding[strlen(black_holding)-1] = NULLCHAR;
3661 /* [HGM] copy holdings to partner-board holdings area */
3662 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3663 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3664 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3667 /* Suppress following prompt */
3668 if (looking_at(buf, &i, "*% ")) {
3669 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3670 savingComment = FALSE;
3678 i++; /* skip unparsed character and loop back */
3681 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3682 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3683 // SendToPlayer(&buf[next_out], i - next_out);
3684 started != STARTED_HOLDINGS && leftover_start > next_out) {
3685 SendToPlayer(&buf[next_out], leftover_start - next_out);
3689 leftover_len = buf_len - leftover_start;
3690 /* if buffer ends with something we couldn't parse,
3691 reparse it after appending the next read */
3693 } else if (count == 0) {
3694 RemoveInputSource(isr);
3695 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3697 DisplayFatalError(_("Error reading from ICS"), error, 1);
3702 /* Board style 12 looks like this:
3704 <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
3706 * The "<12> " is stripped before it gets to this routine. The two
3707 * trailing 0's (flip state and clock ticking) are later addition, and
3708 * some chess servers may not have them, or may have only the first.
3709 * Additional trailing fields may be added in the future.
3712 #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"
3714 #define RELATION_OBSERVING_PLAYED 0
3715 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3716 #define RELATION_PLAYING_MYMOVE 1
3717 #define RELATION_PLAYING_NOTMYMOVE -1
3718 #define RELATION_EXAMINING 2
3719 #define RELATION_ISOLATED_BOARD -3
3720 #define RELATION_STARTING_POSITION -4 /* FICS only */
3723 ParseBoard12(string)
3726 GameMode newGameMode;
3727 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3728 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3729 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3730 char to_play, board_chars[200];
3731 char move_str[500], str[500], elapsed_time[500];
3732 char black[32], white[32];
3734 int prevMove = currentMove;
3737 int fromX, fromY, toX, toY;
3739 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3740 char *bookHit = NULL; // [HGM] book
3741 Boolean weird = FALSE, reqFlag = FALSE;
3743 fromX = fromY = toX = toY = -1;
3747 if (appData.debugMode)
3748 fprintf(debugFP, _("Parsing board: %s\n"), string);
3750 move_str[0] = NULLCHAR;
3751 elapsed_time[0] = NULLCHAR;
3752 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3754 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3755 if(string[i] == ' ') { ranks++; files = 0; }
3757 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3760 for(j = 0; j <i; j++) board_chars[j] = string[j];
3761 board_chars[i] = '\0';
3764 n = sscanf(string, PATTERN, &to_play, &double_push,
3765 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3766 &gamenum, white, black, &relation, &basetime, &increment,
3767 &white_stren, &black_stren, &white_time, &black_time,
3768 &moveNum, str, elapsed_time, move_str, &ics_flip,
3772 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3773 DisplayError(str, 0);
3777 /* Convert the move number to internal form */
3778 moveNum = (moveNum - 1) * 2;
3779 if (to_play == 'B') moveNum++;
3780 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3781 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3787 case RELATION_OBSERVING_PLAYED:
3788 case RELATION_OBSERVING_STATIC:
3789 if (gamenum == -1) {
3790 /* Old ICC buglet */
3791 relation = RELATION_OBSERVING_STATIC;
3793 newGameMode = IcsObserving;
3795 case RELATION_PLAYING_MYMOVE:
3796 case RELATION_PLAYING_NOTMYMOVE:
3798 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3799 IcsPlayingWhite : IcsPlayingBlack;
3801 case RELATION_EXAMINING:
3802 newGameMode = IcsExamining;
3804 case RELATION_ISOLATED_BOARD:
3806 /* Just display this board. If user was doing something else,
3807 we will forget about it until the next board comes. */
3808 newGameMode = IcsIdle;
3810 case RELATION_STARTING_POSITION:
3811 newGameMode = gameMode;
3815 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3816 && newGameMode == IcsObserving && appData.bgObserve) {
3817 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3818 for (k = 0; k < ranks; k++) {
3819 for (j = 0; j < files; j++)
3820 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3821 if(gameInfo.holdingsWidth > 1) {
3822 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3823 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3826 CopyBoard(partnerBoard, board);
3827 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3828 sprintf(partnerStatus, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3829 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3830 DisplayMessage(partnerStatus, "");
3834 /* Modify behavior for initial board display on move listing
3837 switch (ics_getting_history) {
3841 case H_GOT_REQ_HEADER:
3842 case H_GOT_UNREQ_HEADER:
3843 /* This is the initial position of the current game */
3844 gamenum = ics_gamenum;
3845 moveNum = 0; /* old ICS bug workaround */
3846 if (to_play == 'B') {
3847 startedFromSetupPosition = TRUE;
3848 blackPlaysFirst = TRUE;
3850 if (forwardMostMove == 0) forwardMostMove = 1;
3851 if (backwardMostMove == 0) backwardMostMove = 1;
3852 if (currentMove == 0) currentMove = 1;
3854 newGameMode = gameMode;
3855 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3857 case H_GOT_UNWANTED_HEADER:
3858 /* This is an initial board that we don't want */
3860 case H_GETTING_MOVES:
3861 /* Should not happen */
3862 DisplayError(_("Error gathering move list: extra board"), 0);
3863 ics_getting_history = H_FALSE;
3867 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3868 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3869 /* [HGM] We seem to have switched variant unexpectedly
3870 * Try to guess new variant from board size
3872 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3873 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3874 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3875 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3876 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3877 if(!weird) newVariant = VariantNormal;
3878 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3879 /* Get a move list just to see the header, which
3880 will tell us whether this is really bug or zh */
3881 if (ics_getting_history == H_FALSE) {
3882 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3883 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3888 /* Take action if this is the first board of a new game, or of a
3889 different game than is currently being displayed. */
3890 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3891 relation == RELATION_ISOLATED_BOARD) {
3893 /* Forget the old game and get the history (if any) of the new one */
3894 if (gameMode != BeginningOfGame) {
3898 if (appData.autoRaiseBoard) BoardToTop();
3900 if (gamenum == -1) {
3901 newGameMode = IcsIdle;
3902 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3903 appData.getMoveList && !reqFlag) {
3904 /* Need to get game history */
3905 ics_getting_history = H_REQUESTED;
3906 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3910 /* Initially flip the board to have black on the bottom if playing
3911 black or if the ICS flip flag is set, but let the user change
3912 it with the Flip View button. */
3913 flipView = appData.autoFlipView ?
3914 (newGameMode == IcsPlayingBlack) || ics_flip :
3917 /* Done with values from previous mode; copy in new ones */
3918 gameMode = newGameMode;
3920 ics_gamenum = gamenum;
3921 if (gamenum == gs_gamenum) {
3922 int klen = strlen(gs_kind);
3923 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3924 sprintf(str, "ICS %s", gs_kind);
3925 gameInfo.event = StrSave(str);
3927 gameInfo.event = StrSave("ICS game");
3929 gameInfo.site = StrSave(appData.icsHost);
3930 gameInfo.date = PGNDate();
3931 gameInfo.round = StrSave("-");
3932 gameInfo.white = StrSave(white);
3933 gameInfo.black = StrSave(black);
3934 timeControl = basetime * 60 * 1000;
3936 timeIncrement = increment * 1000;
3937 movesPerSession = 0;
3938 gameInfo.timeControl = TimeControlTagValue();
3939 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3940 if (appData.debugMode) {
3941 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3942 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3943 setbuf(debugFP, NULL);
3946 gameInfo.outOfBook = NULL;
3948 /* Do we have the ratings? */
3949 if (strcmp(player1Name, white) == 0 &&
3950 strcmp(player2Name, black) == 0) {
3951 if (appData.debugMode)
3952 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3953 player1Rating, player2Rating);
3954 gameInfo.whiteRating = player1Rating;
3955 gameInfo.blackRating = player2Rating;
3956 } else if (strcmp(player2Name, white) == 0 &&
3957 strcmp(player1Name, black) == 0) {
3958 if (appData.debugMode)
3959 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3960 player2Rating, player1Rating);
3961 gameInfo.whiteRating = player2Rating;
3962 gameInfo.blackRating = player1Rating;
3964 player1Name[0] = player2Name[0] = NULLCHAR;
3966 /* Silence shouts if requested */
3967 if (appData.quietPlay &&
3968 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3969 SendToICS(ics_prefix);
3970 SendToICS("set shout 0\n");
3974 /* Deal with midgame name changes */
3976 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3977 if (gameInfo.white) free(gameInfo.white);
3978 gameInfo.white = StrSave(white);
3980 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3981 if (gameInfo.black) free(gameInfo.black);
3982 gameInfo.black = StrSave(black);
3986 /* Throw away game result if anything actually changes in examine mode */
3987 if (gameMode == IcsExamining && !newGame) {
3988 gameInfo.result = GameUnfinished;
3989 if (gameInfo.resultDetails != NULL) {
3990 free(gameInfo.resultDetails);
3991 gameInfo.resultDetails = NULL;
3995 /* In pausing && IcsExamining mode, we ignore boards coming
3996 in if they are in a different variation than we are. */
3997 if (pauseExamInvalid) return;
3998 if (pausing && gameMode == IcsExamining) {
3999 if (moveNum <= pauseExamForwardMostMove) {
4000 pauseExamInvalid = TRUE;
4001 forwardMostMove = pauseExamForwardMostMove;
4006 if (appData.debugMode) {
4007 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4009 /* Parse the board */
4010 for (k = 0; k < ranks; k++) {
4011 for (j = 0; j < files; j++)
4012 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4013 if(gameInfo.holdingsWidth > 1) {
4014 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4015 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4018 CopyBoard(boards[moveNum], board);
4019 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4021 startedFromSetupPosition =
4022 !CompareBoards(board, initialPosition);
4023 if(startedFromSetupPosition)
4024 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4027 /* [HGM] Set castling rights. Take the outermost Rooks,
4028 to make it also work for FRC opening positions. Note that board12
4029 is really defective for later FRC positions, as it has no way to
4030 indicate which Rook can castle if they are on the same side of King.
4031 For the initial position we grant rights to the outermost Rooks,
4032 and remember thos rights, and we then copy them on positions
4033 later in an FRC game. This means WB might not recognize castlings with
4034 Rooks that have moved back to their original position as illegal,
4035 but in ICS mode that is not its job anyway.
4037 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4038 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4040 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4041 if(board[0][i] == WhiteRook) j = i;
4042 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4043 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4044 if(board[0][i] == WhiteRook) j = i;
4045 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4046 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4047 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4048 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4049 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4050 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4051 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4054 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4055 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4056 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4057 if(board[BOARD_HEIGHT-1][k] == bKing)
4058 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4059 if(gameInfo.variant == VariantTwoKings) {
4060 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4061 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4062 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4065 r = boards[moveNum][CASTLING][0] = initialRights[0];
4066 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4067 r = boards[moveNum][CASTLING][1] = initialRights[1];
4068 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4069 r = boards[moveNum][CASTLING][3] = initialRights[3];
4070 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4071 r = boards[moveNum][CASTLING][4] = initialRights[4];
4072 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4073 /* wildcastle kludge: always assume King has rights */
4074 r = boards[moveNum][CASTLING][2] = initialRights[2];
4075 r = boards[moveNum][CASTLING][5] = initialRights[5];
4077 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4078 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4081 if (ics_getting_history == H_GOT_REQ_HEADER ||
4082 ics_getting_history == H_GOT_UNREQ_HEADER) {
4083 /* This was an initial position from a move list, not
4084 the current position */
4088 /* Update currentMove and known move number limits */
4089 newMove = newGame || moveNum > forwardMostMove;
4092 forwardMostMove = backwardMostMove = currentMove = moveNum;
4093 if (gameMode == IcsExamining && moveNum == 0) {
4094 /* Workaround for ICS limitation: we are not told the wild
4095 type when starting to examine a game. But if we ask for
4096 the move list, the move list header will tell us */
4097 ics_getting_history = H_REQUESTED;
4098 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4101 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4102 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4104 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4105 /* [HGM] applied this also to an engine that is silently watching */
4106 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4107 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4108 gameInfo.variant == currentlyInitializedVariant) {
4109 takeback = forwardMostMove - moveNum;
4110 for (i = 0; i < takeback; i++) {
4111 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4112 SendToProgram("undo\n", &first);
4117 forwardMostMove = moveNum;
4118 if (!pausing || currentMove > forwardMostMove)
4119 currentMove = forwardMostMove;
4121 /* New part of history that is not contiguous with old part */
4122 if (pausing && gameMode == IcsExamining) {
4123 pauseExamInvalid = TRUE;
4124 forwardMostMove = pauseExamForwardMostMove;
4127 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4129 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4130 // [HGM] when we will receive the move list we now request, it will be
4131 // fed to the engine from the first move on. So if the engine is not
4132 // in the initial position now, bring it there.
4133 InitChessProgram(&first, 0);
4136 ics_getting_history = H_REQUESTED;
4137 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4140 forwardMostMove = backwardMostMove = currentMove = moveNum;
4143 /* Update the clocks */
4144 if (strchr(elapsed_time, '.')) {
4146 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4147 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4149 /* Time is in seconds */
4150 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4151 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4156 if (appData.zippyPlay && newGame &&
4157 gameMode != IcsObserving && gameMode != IcsIdle &&
4158 gameMode != IcsExamining)
4159 ZippyFirstBoard(moveNum, basetime, increment);
4162 /* Put the move on the move list, first converting
4163 to canonical algebraic form. */
4165 if (appData.debugMode) {
4166 if (appData.debugMode) { int f = forwardMostMove;
4167 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4168 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4169 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4171 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4172 fprintf(debugFP, "moveNum = %d\n", moveNum);
4173 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4174 setbuf(debugFP, NULL);
4176 if (moveNum <= backwardMostMove) {
4177 /* We don't know what the board looked like before
4179 strcpy(parseList[moveNum - 1], move_str);
4180 strcat(parseList[moveNum - 1], " ");
4181 strcat(parseList[moveNum - 1], elapsed_time);
4182 moveList[moveNum - 1][0] = NULLCHAR;
4183 } else if (strcmp(move_str, "none") == 0) {
4184 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4185 /* Again, we don't know what the board looked like;
4186 this is really the start of the game. */
4187 parseList[moveNum - 1][0] = NULLCHAR;
4188 moveList[moveNum - 1][0] = NULLCHAR;
4189 backwardMostMove = moveNum;
4190 startedFromSetupPosition = TRUE;
4191 fromX = fromY = toX = toY = -1;
4193 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4194 // So we parse the long-algebraic move string in stead of the SAN move
4195 int valid; char buf[MSG_SIZ], *prom;
4197 // str looks something like "Q/a1-a2"; kill the slash
4199 sprintf(buf, "%c%s", str[0], str+2);
4200 else strcpy(buf, str); // might be castling
4201 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4202 strcat(buf, prom); // long move lacks promo specification!
4203 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4204 if(appData.debugMode)
4205 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4206 strcpy(move_str, buf);
4208 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4209 &fromX, &fromY, &toX, &toY, &promoChar)
4210 || ParseOneMove(buf, moveNum - 1, &moveType,
4211 &fromX, &fromY, &toX, &toY, &promoChar);
4212 // end of long SAN patch
4214 (void) CoordsToAlgebraic(boards[moveNum - 1],
4215 PosFlags(moveNum - 1),
4216 fromY, fromX, toY, toX, promoChar,
4217 parseList[moveNum-1]);
4218 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4224 if(gameInfo.variant != VariantShogi)
4225 strcat(parseList[moveNum - 1], "+");
4228 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4229 strcat(parseList[moveNum - 1], "#");
4232 strcat(parseList[moveNum - 1], " ");
4233 strcat(parseList[moveNum - 1], elapsed_time);
4234 /* currentMoveString is set as a side-effect of ParseOneMove */
4235 strcpy(moveList[moveNum - 1], currentMoveString);
4236 strcat(moveList[moveNum - 1], "\n");
4238 /* Move from ICS was illegal!? Punt. */
4239 if (appData.debugMode) {
4240 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4241 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4243 strcpy(parseList[moveNum - 1], move_str);
4244 strcat(parseList[moveNum - 1], " ");
4245 strcat(parseList[moveNum - 1], elapsed_time);
4246 moveList[moveNum - 1][0] = NULLCHAR;
4247 fromX = fromY = toX = toY = -1;
4250 if (appData.debugMode) {
4251 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4252 setbuf(debugFP, NULL);
4256 /* Send move to chess program (BEFORE animating it). */
4257 if (appData.zippyPlay && !newGame && newMove &&
4258 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4260 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4261 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4262 if (moveList[moveNum - 1][0] == NULLCHAR) {
4263 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4265 DisplayError(str, 0);
4267 if (first.sendTime) {
4268 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4270 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4271 if (firstMove && !bookHit) {
4273 if (first.useColors) {
4274 SendToProgram(gameMode == IcsPlayingWhite ?
4276 "black\ngo\n", &first);
4278 SendToProgram("go\n", &first);
4280 first.maybeThinking = TRUE;
4283 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4284 if (moveList[moveNum - 1][0] == NULLCHAR) {
4285 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4286 DisplayError(str, 0);
4288 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4289 SendMoveToProgram(moveNum - 1, &first);
4296 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4297 /* If move comes from a remote source, animate it. If it
4298 isn't remote, it will have already been animated. */
4299 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4300 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4302 if (!pausing && appData.highlightLastMove) {
4303 SetHighlights(fromX, fromY, toX, toY);
4307 /* Start the clocks */
4308 whiteFlag = blackFlag = FALSE;
4309 appData.clockMode = !(basetime == 0 && increment == 0);
4311 ics_clock_paused = TRUE;
4313 } else if (ticking == 1) {
4314 ics_clock_paused = FALSE;
4316 if (gameMode == IcsIdle ||
4317 relation == RELATION_OBSERVING_STATIC ||
4318 relation == RELATION_EXAMINING ||
4320 DisplayBothClocks();
4324 /* Display opponents and material strengths */
4325 if (gameInfo.variant != VariantBughouse &&
4326 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4327 if (tinyLayout || smallLayout) {
4328 if(gameInfo.variant == VariantNormal)
4329 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4330 gameInfo.white, white_stren, gameInfo.black, black_stren,
4331 basetime, increment);
4333 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4334 gameInfo.white, white_stren, gameInfo.black, black_stren,
4335 basetime, increment, (int) gameInfo.variant);
4337 if(gameInfo.variant == VariantNormal)
4338 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4339 gameInfo.white, white_stren, gameInfo.black, black_stren,
4340 basetime, increment);
4342 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4343 gameInfo.white, white_stren, gameInfo.black, black_stren,
4344 basetime, increment, VariantName(gameInfo.variant));
4347 if (appData.debugMode) {
4348 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4353 /* Display the board */
4354 if (!pausing && !appData.noGUI) {
4356 if (appData.premove)
4358 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4359 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4360 ClearPremoveHighlights();
4362 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4363 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4364 DrawPosition(j, boards[currentMove]);
4366 DisplayMove(moveNum - 1);
4367 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4368 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4369 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4370 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4374 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4376 if(bookHit) { // [HGM] book: simulate book reply
4377 static char bookMove[MSG_SIZ]; // a bit generous?
4379 programStats.nodes = programStats.depth = programStats.time =
4380 programStats.score = programStats.got_only_move = 0;
4381 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4383 strcpy(bookMove, "move ");
4384 strcat(bookMove, bookHit);
4385 HandleMachineMove(bookMove, &first);
4394 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4395 ics_getting_history = H_REQUESTED;
4396 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4402 AnalysisPeriodicEvent(force)
4405 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4406 && !force) || !appData.periodicUpdates)
4409 /* Send . command to Crafty to collect stats */
4410 SendToProgram(".\n", &first);
4412 /* Don't send another until we get a response (this makes
4413 us stop sending to old Crafty's which don't understand
4414 the "." command (sending illegal cmds resets node count & time,
4415 which looks bad)) */
4416 programStats.ok_to_send = 0;
4419 void ics_update_width(new_width)
4422 ics_printf("set width %d\n", new_width);
4426 SendMoveToProgram(moveNum, cps)
4428 ChessProgramState *cps;
4432 if (cps->useUsermove) {
4433 SendToProgram("usermove ", cps);
4437 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4438 int len = space - parseList[moveNum];
4439 memcpy(buf, parseList[moveNum], len);
4441 buf[len] = NULLCHAR;
4443 sprintf(buf, "%s\n", parseList[moveNum]);
4445 SendToProgram(buf, cps);
4447 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4448 AlphaRank(moveList[moveNum], 4);
4449 SendToProgram(moveList[moveNum], cps);
4450 AlphaRank(moveList[moveNum], 4); // and back
4452 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4453 * the engine. It would be nice to have a better way to identify castle
4455 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4456 && cps->useOOCastle) {
4457 int fromX = moveList[moveNum][0] - AAA;
4458 int fromY = moveList[moveNum][1] - ONE;
4459 int toX = moveList[moveNum][2] - AAA;
4460 int toY = moveList[moveNum][3] - ONE;
4461 if((boards[moveNum][fromY][fromX] == WhiteKing
4462 && boards[moveNum][toY][toX] == WhiteRook)
4463 || (boards[moveNum][fromY][fromX] == BlackKing
4464 && boards[moveNum][toY][toX] == BlackRook)) {
4465 if(toX > fromX) SendToProgram("O-O\n", cps);
4466 else SendToProgram("O-O-O\n", cps);
4468 else SendToProgram(moveList[moveNum], cps);
4470 else SendToProgram(moveList[moveNum], cps);
4471 /* End of additions by Tord */
4474 /* [HGM] setting up the opening has brought engine in force mode! */
4475 /* Send 'go' if we are in a mode where machine should play. */
4476 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4477 (gameMode == TwoMachinesPlay ||
4479 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4481 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4482 SendToProgram("go\n", cps);
4483 if (appData.debugMode) {
4484 fprintf(debugFP, "(extra)\n");
4487 setboardSpoiledMachineBlack = 0;
4491 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4493 int fromX, fromY, toX, toY;
4495 char user_move[MSG_SIZ];
4499 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4500 (int)moveType, fromX, fromY, toX, toY);
4501 DisplayError(user_move + strlen("say "), 0);
4503 case WhiteKingSideCastle:
4504 case BlackKingSideCastle:
4505 case WhiteQueenSideCastleWild:
4506 case BlackQueenSideCastleWild:
4508 case WhiteHSideCastleFR:
4509 case BlackHSideCastleFR:
4511 sprintf(user_move, "o-o\n");
4513 case WhiteQueenSideCastle:
4514 case BlackQueenSideCastle:
4515 case WhiteKingSideCastleWild:
4516 case BlackKingSideCastleWild:
4518 case WhiteASideCastleFR:
4519 case BlackASideCastleFR:
4521 sprintf(user_move, "o-o-o\n");
4523 case WhitePromotionQueen:
4524 case BlackPromotionQueen:
4525 case WhitePromotionRook:
4526 case BlackPromotionRook:
4527 case WhitePromotionBishop:
4528 case BlackPromotionBishop:
4529 case WhitePromotionKnight:
4530 case BlackPromotionKnight:
4531 case WhitePromotionKing:
4532 case BlackPromotionKing:
4533 case WhitePromotionChancellor:
4534 case BlackPromotionChancellor:
4535 case WhitePromotionArchbishop:
4536 case BlackPromotionArchbishop:
4537 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4538 sprintf(user_move, "%c%c%c%c=%c\n",
4539 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4540 PieceToChar(WhiteFerz));
4541 else if(gameInfo.variant == VariantGreat)
4542 sprintf(user_move, "%c%c%c%c=%c\n",
4543 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4544 PieceToChar(WhiteMan));
4546 sprintf(user_move, "%c%c%c%c=%c\n",
4547 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4548 PieceToChar(PromoPiece(moveType)));
4552 sprintf(user_move, "%c@%c%c\n",
4553 ToUpper(PieceToChar((ChessSquare) fromX)),
4554 AAA + toX, ONE + toY);
4557 case WhiteCapturesEnPassant:
4558 case BlackCapturesEnPassant:
4559 case IllegalMove: /* could be a variant we don't quite understand */
4560 sprintf(user_move, "%c%c%c%c\n",
4561 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4564 SendToICS(user_move);
4565 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4566 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4571 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4572 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4573 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4574 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4575 DisplayError("You cannot do this while you are playing or observing", 0);
4578 if(gameMode != IcsExamining) { // is this ever not the case?
4579 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4581 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4582 sprintf(command, "match %s", ics_handle);
4583 } else { // on FICS we must first go to general examine mode
4584 strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4586 if(gameInfo.variant != VariantNormal) {
4587 // try figure out wild number, as xboard names are not always valid on ICS
4588 for(i=1; i<=36; i++) {
4589 sprintf(buf, "wild/%d", i);
4590 if(StringToVariant(buf) == gameInfo.variant) break;
4592 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4593 else if(i == 22) sprintf(buf, "%s fr\n", command);
4594 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4595 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4596 SendToICS(ics_prefix);
4598 if(startedFromSetupPosition || backwardMostMove != 0) {
4599 fen = PositionToFEN(backwardMostMove, NULL);
4600 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4601 sprintf(buf, "loadfen %s\n", fen);
4603 } else { // FICS: everything has to set by separate bsetup commands
4604 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4605 sprintf(buf, "bsetup fen %s\n", fen);
4607 if(!WhiteOnMove(backwardMostMove)) {
4608 SendToICS("bsetup tomove black\n");
4610 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4611 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4613 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4614 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4616 i = boards[backwardMostMove][EP_STATUS];
4617 if(i >= 0) { // set e.p.
4618 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4624 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4625 SendToICS("bsetup done\n"); // switch to normal examining.
4627 for(i = backwardMostMove; i<last; i++) {
4629 sprintf(buf, "%s\n", parseList[i]);
4632 SendToICS(ics_prefix);
4633 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4637 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4642 if (rf == DROP_RANK) {
4643 sprintf(move, "%c@%c%c\n",
4644 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4646 if (promoChar == 'x' || promoChar == NULLCHAR) {
4647 sprintf(move, "%c%c%c%c\n",
4648 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4650 sprintf(move, "%c%c%c%c%c\n",
4651 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4657 ProcessICSInitScript(f)
4662 while (fgets(buf, MSG_SIZ, f)) {
4663 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4670 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4672 AlphaRank(char *move, int n)
4674 // char *p = move, c; int x, y;
4676 if (appData.debugMode) {
4677 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4681 move[2]>='0' && move[2]<='9' &&
4682 move[3]>='a' && move[3]<='x' ) {
4684 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4685 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4687 if(move[0]>='0' && move[0]<='9' &&
4688 move[1]>='a' && move[1]<='x' &&
4689 move[2]>='0' && move[2]<='9' &&
4690 move[3]>='a' && move[3]<='x' ) {
4691 /* input move, Shogi -> normal */
4692 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4693 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4694 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4695 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4698 move[3]>='0' && move[3]<='9' &&
4699 move[2]>='a' && move[2]<='x' ) {
4701 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4702 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4705 move[0]>='a' && move[0]<='x' &&
4706 move[3]>='0' && move[3]<='9' &&
4707 move[2]>='a' && move[2]<='x' ) {
4708 /* output move, normal -> Shogi */
4709 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4710 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4711 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4712 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4713 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4715 if (appData.debugMode) {
4716 fprintf(debugFP, " out = '%s'\n", move);
4720 char yy_textstr[8000];
4722 /* Parser for moves from gnuchess, ICS, or user typein box */
4724 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4727 ChessMove *moveType;
4728 int *fromX, *fromY, *toX, *toY;
4731 if (appData.debugMode) {
4732 fprintf(debugFP, "move to parse: %s\n", move);
4734 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4736 switch (*moveType) {
4737 case WhitePromotionChancellor:
4738 case BlackPromotionChancellor:
4739 case WhitePromotionArchbishop:
4740 case BlackPromotionArchbishop:
4741 case WhitePromotionQueen:
4742 case BlackPromotionQueen:
4743 case WhitePromotionRook:
4744 case BlackPromotionRook:
4745 case WhitePromotionBishop:
4746 case BlackPromotionBishop:
4747 case WhitePromotionKnight:
4748 case BlackPromotionKnight:
4749 case WhitePromotionKing:
4750 case BlackPromotionKing:
4752 case WhiteCapturesEnPassant:
4753 case BlackCapturesEnPassant:
4754 case WhiteKingSideCastle:
4755 case WhiteQueenSideCastle:
4756 case BlackKingSideCastle:
4757 case BlackQueenSideCastle:
4758 case WhiteKingSideCastleWild:
4759 case WhiteQueenSideCastleWild:
4760 case BlackKingSideCastleWild:
4761 case BlackQueenSideCastleWild:
4762 /* Code added by Tord: */
4763 case WhiteHSideCastleFR:
4764 case WhiteASideCastleFR:
4765 case BlackHSideCastleFR:
4766 case BlackASideCastleFR:
4767 /* End of code added by Tord */
4768 case IllegalMove: /* bug or odd chess variant */
4769 *fromX = currentMoveString[0] - AAA;
4770 *fromY = currentMoveString[1] - ONE;
4771 *toX = currentMoveString[2] - AAA;
4772 *toY = currentMoveString[3] - ONE;
4773 *promoChar = currentMoveString[4];
4774 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4775 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4776 if (appData.debugMode) {
4777 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4779 *fromX = *fromY = *toX = *toY = 0;
4782 if (appData.testLegality) {
4783 return (*moveType != IllegalMove);
4785 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4786 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4791 *fromX = *moveType == WhiteDrop ?
4792 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4793 (int) CharToPiece(ToLower(currentMoveString[0]));
4795 *toX = currentMoveString[2] - AAA;
4796 *toY = currentMoveString[3] - ONE;
4797 *promoChar = NULLCHAR;
4801 case ImpossibleMove:
4802 case (ChessMove) 0: /* end of file */
4811 if (appData.debugMode) {
4812 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4815 *fromX = *fromY = *toX = *toY = 0;
4816 *promoChar = NULLCHAR;
4823 ParsePV(char *pv, Boolean storeComments)
4824 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4825 int fromX, fromY, toX, toY; char promoChar;
4830 endPV = forwardMostMove;
4832 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4833 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4834 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4835 if(appData.debugMode){
4836 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);
4838 if(!valid && nr == 0 &&
4839 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4840 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4841 // Hande case where played move is different from leading PV move
4842 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4843 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4844 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4845 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4846 endPV += 2; // if position different, keep this
4847 moveList[endPV-1][0] = fromX + AAA;
4848 moveList[endPV-1][1] = fromY + ONE;
4849 moveList[endPV-1][2] = toX + AAA;
4850 moveList[endPV-1][3] = toY + ONE;
4851 parseList[endPV-1][0] = NULLCHAR;
4852 strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4855 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4856 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4857 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4858 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4859 valid++; // allow comments in PV
4863 if(endPV+1 > framePtr) break; // no space, truncate
4866 CopyBoard(boards[endPV], boards[endPV-1]);
4867 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4868 moveList[endPV-1][0] = fromX + AAA;
4869 moveList[endPV-1][1] = fromY + ONE;
4870 moveList[endPV-1][2] = toX + AAA;
4871 moveList[endPV-1][3] = toY + ONE;
4873 CoordsToAlgebraic(boards[endPV - 1],
4874 PosFlags(endPV - 1),
4875 fromY, fromX, toY, toX, promoChar,
4876 parseList[endPV - 1]);
4878 parseList[endPV-1][0] = NULLCHAR;
4880 currentMove = endPV;
4881 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4882 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4883 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4884 DrawPosition(TRUE, boards[currentMove]);
4887 static int lastX, lastY;
4890 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4895 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4896 lastX = x; lastY = y;
4897 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4899 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4900 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4902 do{ while(buf[index] && buf[index] != '\n') index++;
4903 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4905 ParsePV(buf+startPV, FALSE);
4906 *start = startPV; *end = index-1;
4911 LoadPV(int x, int y)
4912 { // called on right mouse click to load PV
4913 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4914 lastX = x; lastY = y;
4915 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4922 if(endPV < 0) return;
4924 currentMove = forwardMostMove;
4925 ClearPremoveHighlights();
4926 DrawPosition(TRUE, boards[currentMove]);
4930 MovePV(int x, int y, int h)
4931 { // step through PV based on mouse coordinates (called on mouse move)
4932 int margin = h>>3, step = 0;
4934 if(endPV < 0) return;
4935 // we must somehow check if right button is still down (might be released off board!)
4936 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4937 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4938 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4940 lastX = x; lastY = y;
4941 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4942 currentMove += step;
4943 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4944 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4945 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4946 DrawPosition(FALSE, boards[currentMove]);
4950 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4951 // All positions will have equal probability, but the current method will not provide a unique
4952 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4958 int piecesLeft[(int)BlackPawn];
4959 int seed, nrOfShuffles;
4961 void GetPositionNumber()
4962 { // sets global variable seed
4965 seed = appData.defaultFrcPosition;
4966 if(seed < 0) { // randomize based on time for negative FRC position numbers
4967 for(i=0; i<50; i++) seed += random();
4968 seed = random() ^ random() >> 8 ^ random() << 8;
4969 if(seed<0) seed = -seed;
4973 int put(Board board, int pieceType, int rank, int n, int shade)
4974 // put the piece on the (n-1)-th empty squares of the given shade
4978 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4979 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4980 board[rank][i] = (ChessSquare) pieceType;
4981 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4983 piecesLeft[pieceType]--;
4991 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4992 // calculate where the next piece goes, (any empty square), and put it there
4996 i = seed % squaresLeft[shade];
4997 nrOfShuffles *= squaresLeft[shade];
4998 seed /= squaresLeft[shade];
4999 put(board, pieceType, rank, i, shade);
5002 void AddTwoPieces(Board board, int pieceType, int rank)
5003 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5005 int i, n=squaresLeft[ANY], j=n-1, k;
5007 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5008 i = seed % k; // pick one
5011 while(i >= j) i -= j--;
5012 j = n - 1 - j; i += j;
5013 put(board, pieceType, rank, j, ANY);
5014 put(board, pieceType, rank, i, ANY);
5017 void SetUpShuffle(Board board, int number)
5021 GetPositionNumber(); nrOfShuffles = 1;
5023 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5024 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5025 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5027 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5029 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5030 p = (int) board[0][i];
5031 if(p < (int) BlackPawn) piecesLeft[p] ++;
5032 board[0][i] = EmptySquare;
5035 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5036 // shuffles restricted to allow normal castling put KRR first
5037 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5038 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5039 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5040 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5041 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5042 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5043 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5044 put(board, WhiteRook, 0, 0, ANY);
5045 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5048 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5049 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5050 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5051 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5052 while(piecesLeft[p] >= 2) {
5053 AddOnePiece(board, p, 0, LITE);
5054 AddOnePiece(board, p, 0, DARK);
5056 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5059 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5060 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5061 // but we leave King and Rooks for last, to possibly obey FRC restriction
5062 if(p == (int)WhiteRook) continue;
5063 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5064 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5067 // now everything is placed, except perhaps King (Unicorn) and Rooks
5069 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5070 // Last King gets castling rights
5071 while(piecesLeft[(int)WhiteUnicorn]) {
5072 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5073 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5076 while(piecesLeft[(int)WhiteKing]) {
5077 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5078 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5083 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5084 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5087 // Only Rooks can be left; simply place them all
5088 while(piecesLeft[(int)WhiteRook]) {
5089 i = put(board, WhiteRook, 0, 0, ANY);
5090 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5093 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5095 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5098 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5099 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5102 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5105 int SetCharTable( char *table, const char * map )
5106 /* [HGM] moved here from winboard.c because of its general usefulness */
5107 /* Basically a safe strcpy that uses the last character as King */
5109 int result = FALSE; int NrPieces;
5111 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5112 && NrPieces >= 12 && !(NrPieces&1)) {
5113 int i; /* [HGM] Accept even length from 12 to 34 */
5115 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5116 for( i=0; i<NrPieces/2-1; i++ ) {
5118 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5120 table[(int) WhiteKing] = map[NrPieces/2-1];
5121 table[(int) BlackKing] = map[NrPieces-1];
5129 void Prelude(Board board)
5130 { // [HGM] superchess: random selection of exo-pieces
5131 int i, j, k; ChessSquare p;
5132 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5134 GetPositionNumber(); // use FRC position number
5136 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5137 SetCharTable(pieceToChar, appData.pieceToCharTable);
5138 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5139 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5142 j = seed%4; seed /= 4;
5143 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5144 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5145 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5146 j = seed%3 + (seed%3 >= j); seed /= 3;
5147 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5148 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5149 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5150 j = seed%3; seed /= 3;
5151 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5152 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5153 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5154 j = seed%2 + (seed%2 >= j); seed /= 2;
5155 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5156 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5157 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5158 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5159 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5160 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5161 put(board, exoPieces[0], 0, 0, ANY);
5162 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5166 InitPosition(redraw)
5169 ChessSquare (* pieces)[BOARD_FILES];
5170 int i, j, pawnRow, overrule,
5171 oldx = gameInfo.boardWidth,
5172 oldy = gameInfo.boardHeight,
5173 oldh = gameInfo.holdingsWidth,
5174 oldv = gameInfo.variant;
5176 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5178 /* [AS] Initialize pv info list [HGM] and game status */
5180 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5181 pvInfoList[i].depth = 0;
5182 boards[i][EP_STATUS] = EP_NONE;
5183 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5186 initialRulePlies = 0; /* 50-move counter start */
5188 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5189 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5193 /* [HGM] logic here is completely changed. In stead of full positions */
5194 /* the initialized data only consist of the two backranks. The switch */
5195 /* selects which one we will use, which is than copied to the Board */
5196 /* initialPosition, which for the rest is initialized by Pawns and */
5197 /* empty squares. This initial position is then copied to boards[0], */
5198 /* possibly after shuffling, so that it remains available. */
5200 gameInfo.holdingsWidth = 0; /* default board sizes */
5201 gameInfo.boardWidth = 8;
5202 gameInfo.boardHeight = 8;
5203 gameInfo.holdingsSize = 0;
5204 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5205 for(i=0; i<BOARD_FILES-2; i++)
5206 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5207 initialPosition[EP_STATUS] = EP_NONE;
5208 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5210 switch (gameInfo.variant) {
5211 case VariantFischeRandom:
5212 shuffleOpenings = TRUE;
5216 case VariantShatranj:
5217 pieces = ShatranjArray;
5218 nrCastlingRights = 0;
5219 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5222 pieces = makrukArray;
5223 nrCastlingRights = 0;
5224 startedFromSetupPosition = TRUE;
5225 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5227 case VariantTwoKings:
5228 pieces = twoKingsArray;
5230 case VariantCapaRandom:
5231 shuffleOpenings = TRUE;
5232 case VariantCapablanca:
5233 pieces = CapablancaArray;
5234 gameInfo.boardWidth = 10;
5235 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5238 pieces = GothicArray;
5239 gameInfo.boardWidth = 10;
5240 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5243 pieces = JanusArray;
5244 gameInfo.boardWidth = 10;
5245 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5246 nrCastlingRights = 6;
5247 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5248 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5249 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5250 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5251 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5252 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5255 pieces = FalconArray;
5256 gameInfo.boardWidth = 10;
5257 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5259 case VariantXiangqi:
5260 pieces = XiangqiArray;
5261 gameInfo.boardWidth = 9;
5262 gameInfo.boardHeight = 10;
5263 nrCastlingRights = 0;
5264 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5267 pieces = ShogiArray;
5268 gameInfo.boardWidth = 9;
5269 gameInfo.boardHeight = 9;
5270 gameInfo.holdingsSize = 7;
5271 nrCastlingRights = 0;
5272 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5274 case VariantCourier:
5275 pieces = CourierArray;
5276 gameInfo.boardWidth = 12;
5277 nrCastlingRights = 0;
5278 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5280 case VariantKnightmate:
5281 pieces = KnightmateArray;
5282 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5285 pieces = fairyArray;
5286 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5289 pieces = GreatArray;
5290 gameInfo.boardWidth = 10;
5291 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5292 gameInfo.holdingsSize = 8;
5296 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5297 gameInfo.holdingsSize = 8;
5298 startedFromSetupPosition = TRUE;
5300 case VariantCrazyhouse:
5301 case VariantBughouse:
5303 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5304 gameInfo.holdingsSize = 5;
5306 case VariantWildCastle:
5308 /* !!?shuffle with kings guaranteed to be on d or e file */
5309 shuffleOpenings = 1;
5311 case VariantNoCastle:
5313 nrCastlingRights = 0;
5314 /* !!?unconstrained back-rank shuffle */
5315 shuffleOpenings = 1;
5320 if(appData.NrFiles >= 0) {
5321 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5322 gameInfo.boardWidth = appData.NrFiles;
5324 if(appData.NrRanks >= 0) {
5325 gameInfo.boardHeight = appData.NrRanks;
5327 if(appData.holdingsSize >= 0) {
5328 i = appData.holdingsSize;
5329 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5330 gameInfo.holdingsSize = i;
5332 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5333 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5334 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5336 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5337 if(pawnRow < 1) pawnRow = 1;
5338 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5340 /* User pieceToChar list overrules defaults */
5341 if(appData.pieceToCharTable != NULL)
5342 SetCharTable(pieceToChar, appData.pieceToCharTable);
5344 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5346 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5347 s = (ChessSquare) 0; /* account holding counts in guard band */
5348 for( i=0; i<BOARD_HEIGHT; i++ )
5349 initialPosition[i][j] = s;
5351 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5352 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5353 initialPosition[pawnRow][j] = WhitePawn;
5354 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5355 if(gameInfo.variant == VariantXiangqi) {
5357 initialPosition[pawnRow][j] =
5358 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5359 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5360 initialPosition[2][j] = WhiteCannon;
5361 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5365 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5367 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5370 initialPosition[1][j] = WhiteBishop;
5371 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5373 initialPosition[1][j] = WhiteRook;
5374 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5377 if( nrCastlingRights == -1) {
5378 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5379 /* This sets default castling rights from none to normal corners */
5380 /* Variants with other castling rights must set them themselves above */
5381 nrCastlingRights = 6;
5383 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5384 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5385 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5386 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5387 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5388 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5391 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5392 if(gameInfo.variant == VariantGreat) { // promotion commoners
5393 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5394 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5395 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5396 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5398 if (appData.debugMode) {
5399 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5401 if(shuffleOpenings) {
5402 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5403 startedFromSetupPosition = TRUE;
5405 if(startedFromPositionFile) {
5406 /* [HGM] loadPos: use PositionFile for every new game */
5407 CopyBoard(initialPosition, filePosition);
5408 for(i=0; i<nrCastlingRights; i++)
5409 initialRights[i] = filePosition[CASTLING][i];
5410 startedFromSetupPosition = TRUE;
5413 CopyBoard(boards[0], initialPosition);
5415 if(oldx != gameInfo.boardWidth ||
5416 oldy != gameInfo.boardHeight ||
5417 oldh != gameInfo.holdingsWidth
5419 || oldv == VariantGothic || // For licensing popups
5420 gameInfo.variant == VariantGothic
5423 || oldv == VariantFalcon ||
5424 gameInfo.variant == VariantFalcon
5427 InitDrawingSizes(-2 ,0);
5430 DrawPosition(TRUE, boards[currentMove]);
5434 SendBoard(cps, moveNum)
5435 ChessProgramState *cps;
5438 char message[MSG_SIZ];
5440 if (cps->useSetboard) {
5441 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5442 sprintf(message, "setboard %s\n", fen);
5443 SendToProgram(message, cps);
5449 /* Kludge to set black to move, avoiding the troublesome and now
5450 * deprecated "black" command.
5452 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5454 SendToProgram("edit\n", cps);
5455 SendToProgram("#\n", cps);
5456 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5457 bp = &boards[moveNum][i][BOARD_LEFT];
5458 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5459 if ((int) *bp < (int) BlackPawn) {
5460 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5462 if(message[0] == '+' || message[0] == '~') {
5463 sprintf(message, "%c%c%c+\n",
5464 PieceToChar((ChessSquare)(DEMOTED *bp)),
5467 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5468 message[1] = BOARD_RGHT - 1 - j + '1';
5469 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5471 SendToProgram(message, cps);
5476 SendToProgram("c\n", cps);
5477 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5478 bp = &boards[moveNum][i][BOARD_LEFT];
5479 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5480 if (((int) *bp != (int) EmptySquare)
5481 && ((int) *bp >= (int) BlackPawn)) {
5482 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5484 if(message[0] == '+' || message[0] == '~') {
5485 sprintf(message, "%c%c%c+\n",
5486 PieceToChar((ChessSquare)(DEMOTED *bp)),
5489 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5490 message[1] = BOARD_RGHT - 1 - j + '1';
5491 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5493 SendToProgram(message, cps);
5498 SendToProgram(".\n", cps);
5500 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5503 static int autoQueen; // [HGM] oneclick
5506 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5508 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5509 /* [HGM] add Shogi promotions */
5510 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5515 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5516 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5518 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5519 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5522 piece = boards[currentMove][fromY][fromX];
5523 if(gameInfo.variant == VariantShogi) {
5524 promotionZoneSize = 3;
5525 highestPromotingPiece = (int)WhiteFerz;
5526 } else if(gameInfo.variant == VariantMakruk) {
5527 promotionZoneSize = 3;
5530 // next weed out all moves that do not touch the promotion zone at all
5531 if((int)piece >= BlackPawn) {
5532 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5534 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5536 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5537 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5540 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5542 // weed out mandatory Shogi promotions
5543 if(gameInfo.variant == VariantShogi) {
5544 if(piece >= BlackPawn) {
5545 if(toY == 0 && piece == BlackPawn ||
5546 toY == 0 && piece == BlackQueen ||
5547 toY <= 1 && piece == BlackKnight) {
5552 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5553 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5554 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5561 // weed out obviously illegal Pawn moves
5562 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5563 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5564 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5565 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5566 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5567 // note we are not allowed to test for valid (non-)capture, due to premove
5570 // we either have a choice what to promote to, or (in Shogi) whether to promote
5571 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5572 *promoChoice = PieceToChar(BlackFerz); // no choice
5575 if(autoQueen) { // predetermined
5576 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5577 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5578 else *promoChoice = PieceToChar(BlackQueen);
5582 // suppress promotion popup on illegal moves that are not premoves
5583 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5584 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5585 if(appData.testLegality && !premove) {
5586 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5587 fromY, fromX, toY, toX, NULLCHAR);
5588 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5589 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5597 InPalace(row, column)
5599 { /* [HGM] for Xiangqi */
5600 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5601 column < (BOARD_WIDTH + 4)/2 &&
5602 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5607 PieceForSquare (x, y)
5611 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5614 return boards[currentMove][y][x];
5618 OKToStartUserMove(x, y)
5621 ChessSquare from_piece;
5624 if (matchMode) return FALSE;
5625 if (gameMode == EditPosition) return TRUE;
5627 if (x >= 0 && y >= 0)
5628 from_piece = boards[currentMove][y][x];
5630 from_piece = EmptySquare;
5632 if (from_piece == EmptySquare) return FALSE;
5634 white_piece = (int)from_piece >= (int)WhitePawn &&
5635 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5638 case PlayFromGameFile:
5640 case TwoMachinesPlay:
5648 case MachinePlaysWhite:
5649 case IcsPlayingBlack:
5650 if (appData.zippyPlay) return FALSE;
5652 DisplayMoveError(_("You are playing Black"));
5657 case MachinePlaysBlack:
5658 case IcsPlayingWhite:
5659 if (appData.zippyPlay) return FALSE;
5661 DisplayMoveError(_("You are playing White"));
5667 if (!white_piece && WhiteOnMove(currentMove)) {
5668 DisplayMoveError(_("It is White's turn"));
5671 if (white_piece && !WhiteOnMove(currentMove)) {
5672 DisplayMoveError(_("It is Black's turn"));
5675 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5676 /* Editing correspondence game history */
5677 /* Could disallow this or prompt for confirmation */
5682 case BeginningOfGame:
5683 if (appData.icsActive) return FALSE;
5684 if (!appData.noChessProgram) {
5686 DisplayMoveError(_("You are playing White"));
5693 if (!white_piece && WhiteOnMove(currentMove)) {
5694 DisplayMoveError(_("It is White's turn"));
5697 if (white_piece && !WhiteOnMove(currentMove)) {
5698 DisplayMoveError(_("It is Black's turn"));
5707 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5708 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5709 && gameMode != AnalyzeFile && gameMode != Training) {
5710 DisplayMoveError(_("Displayed position is not current"));
5717 OnlyMove(int *x, int *y, Boolean captures) {
5718 DisambiguateClosure cl;
5719 if (appData.zippyPlay) return FALSE;
5721 case MachinePlaysBlack:
5722 case IcsPlayingWhite:
5723 case BeginningOfGame:
5724 if(!WhiteOnMove(currentMove)) return FALSE;
5726 case MachinePlaysWhite:
5727 case IcsPlayingBlack:
5728 if(WhiteOnMove(currentMove)) return FALSE;
5733 cl.pieceIn = EmptySquare;
5738 cl.promoCharIn = NULLCHAR;
5739 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5740 if( cl.kind == NormalMove ||
5741 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5742 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5743 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5744 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5751 if(cl.kind != ImpossibleMove) return FALSE;
5752 cl.pieceIn = EmptySquare;
5757 cl.promoCharIn = NULLCHAR;
5758 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5759 if( cl.kind == NormalMove ||
5760 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5761 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5762 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5763 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5768 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5774 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5775 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5776 int lastLoadGameUseList = FALSE;
5777 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5778 ChessMove lastLoadGameStart = (ChessMove) 0;
5781 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5782 int fromX, fromY, toX, toY;
5787 ChessSquare pdown, pup;
5789 /* Check if the user is playing in turn. This is complicated because we
5790 let the user "pick up" a piece before it is his turn. So the piece he
5791 tried to pick up may have been captured by the time he puts it down!
5792 Therefore we use the color the user is supposed to be playing in this
5793 test, not the color of the piece that is currently on the starting
5794 square---except in EditGame mode, where the user is playing both
5795 sides; fortunately there the capture race can't happen. (It can
5796 now happen in IcsExamining mode, but that's just too bad. The user
5797 will get a somewhat confusing message in that case.)
5801 case PlayFromGameFile:
5803 case TwoMachinesPlay:
5807 /* We switched into a game mode where moves are not accepted,
5808 perhaps while the mouse button was down. */
5809 return ImpossibleMove;
5811 case MachinePlaysWhite:
5812 /* User is moving for Black */
5813 if (WhiteOnMove(currentMove)) {
5814 DisplayMoveError(_("It is White's turn"));
5815 return ImpossibleMove;
5819 case MachinePlaysBlack:
5820 /* User is moving for White */
5821 if (!WhiteOnMove(currentMove)) {
5822 DisplayMoveError(_("It is Black's turn"));
5823 return ImpossibleMove;
5829 case BeginningOfGame:
5832 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5833 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5834 /* User is moving for Black */
5835 if (WhiteOnMove(currentMove)) {
5836 DisplayMoveError(_("It is White's turn"));
5837 return ImpossibleMove;
5840 /* User is moving for White */
5841 if (!WhiteOnMove(currentMove)) {
5842 DisplayMoveError(_("It is Black's turn"));
5843 return ImpossibleMove;
5848 case IcsPlayingBlack:
5849 /* User is moving for Black */
5850 if (WhiteOnMove(currentMove)) {
5851 if (!appData.premove) {
5852 DisplayMoveError(_("It is White's turn"));
5853 } else if (toX >= 0 && toY >= 0) {
5856 premoveFromX = fromX;
5857 premoveFromY = fromY;
5858 premovePromoChar = promoChar;
5860 if (appData.debugMode)
5861 fprintf(debugFP, "Got premove: fromX %d,"
5862 "fromY %d, toX %d, toY %d\n",
5863 fromX, fromY, toX, toY);
5865 return ImpossibleMove;
5869 case IcsPlayingWhite:
5870 /* User is moving for White */
5871 if (!WhiteOnMove(currentMove)) {
5872 if (!appData.premove) {
5873 DisplayMoveError(_("It is Black's turn"));
5874 } else if (toX >= 0 && toY >= 0) {
5877 premoveFromX = fromX;
5878 premoveFromY = fromY;
5879 premovePromoChar = promoChar;
5881 if (appData.debugMode)
5882 fprintf(debugFP, "Got premove: fromX %d,"
5883 "fromY %d, toX %d, toY %d\n",
5884 fromX, fromY, toX, toY);
5886 return ImpossibleMove;
5894 /* EditPosition, empty square, or different color piece;
5895 click-click move is possible */
5896 if (toX == -2 || toY == -2) {
5897 boards[0][fromY][fromX] = EmptySquare;
5898 return AmbiguousMove;
5899 } else if (toX >= 0 && toY >= 0) {
5900 boards[0][toY][toX] = boards[0][fromY][fromX];
5901 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5902 if(boards[0][fromY][0] != EmptySquare) {
5903 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5904 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5907 if(fromX == BOARD_RGHT+1) {
5908 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5909 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5910 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5913 boards[0][fromY][fromX] = EmptySquare;
5914 return AmbiguousMove;
5916 return ImpossibleMove;
5919 if(toX < 0 || toY < 0) return ImpossibleMove;
5920 pdown = boards[currentMove][fromY][fromX];
5921 pup = boards[currentMove][toY][toX];
5923 /* [HGM] If move started in holdings, it means a drop */
5924 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5925 if( pup != EmptySquare ) return ImpossibleMove;
5926 if(appData.testLegality) {
5927 /* it would be more logical if LegalityTest() also figured out
5928 * which drops are legal. For now we forbid pawns on back rank.
5929 * Shogi is on its own here...
5931 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5932 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5933 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5935 return WhiteDrop; /* Not needed to specify white or black yet */
5938 /* [HGM] always test for legality, to get promotion info */
5939 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5940 fromY, fromX, toY, toX, promoChar);
5941 /* [HGM] but possibly ignore an IllegalMove result */
5942 if (appData.testLegality) {
5943 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5944 DisplayMoveError(_("Illegal move"));
5945 return ImpossibleMove;
5950 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5951 function is made into one that returns an OK move type if FinishMove
5952 should be called. This to give the calling driver routine the
5953 opportunity to finish the userMove input with a promotion popup,
5954 without bothering the user with this for invalid or illegal moves */
5956 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5959 /* Common tail of UserMoveEvent and DropMenuEvent */
5961 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5963 int fromX, fromY, toX, toY;
5964 /*char*/int promoChar;
5968 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5969 // [HGM] superchess: suppress promotions to non-available piece
5970 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5971 if(WhiteOnMove(currentMove)) {
5972 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5974 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5978 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5979 move type in caller when we know the move is a legal promotion */
5980 if(moveType == NormalMove && promoChar)
5981 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5983 /* [HGM] convert drag-and-drop piece drops to standard form */
5984 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5985 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5986 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5987 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5988 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5989 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5990 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5991 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5995 /* [HGM] <popupFix> The following if has been moved here from
5996 UserMoveEvent(). Because it seemed to belong here (why not allow
5997 piece drops in training games?), and because it can only be
5998 performed after it is known to what we promote. */
5999 if (gameMode == Training) {
6000 /* compare the move played on the board to the next move in the
6001 * game. If they match, display the move and the opponent's response.
6002 * If they don't match, display an error message.
6006 CopyBoard(testBoard, boards[currentMove]);
6007 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6009 if (CompareBoards(testBoard, boards[currentMove+1])) {
6010 ForwardInner(currentMove+1);
6012 /* Autoplay the opponent's response.
6013 * if appData.animate was TRUE when Training mode was entered,
6014 * the response will be animated.
6016 saveAnimate = appData.animate;
6017 appData.animate = animateTraining;
6018 ForwardInner(currentMove+1);
6019 appData.animate = saveAnimate;
6021 /* check for the end of the game */
6022 if (currentMove >= forwardMostMove) {
6023 gameMode = PlayFromGameFile;
6025 SetTrainingModeOff();
6026 DisplayInformation(_("End of game"));
6029 DisplayError(_("Incorrect move"), 0);
6034 /* Ok, now we know that the move is good, so we can kill
6035 the previous line in Analysis Mode */
6036 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6037 && currentMove < forwardMostMove) {
6038 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6041 /* If we need the chess program but it's dead, restart it */
6042 ResurrectChessProgram();
6044 /* A user move restarts a paused game*/
6048 thinkOutput[0] = NULLCHAR;
6050 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6052 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6054 if (gameMode == BeginningOfGame) {
6055 if (appData.noChessProgram) {
6056 gameMode = EditGame;
6060 gameMode = MachinePlaysBlack;
6063 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6065 if (first.sendName) {
6066 sprintf(buf, "name %s\n", gameInfo.white);
6067 SendToProgram(buf, &first);
6074 /* Relay move to ICS or chess engine */
6075 if (appData.icsActive) {
6076 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6077 gameMode == IcsExamining) {
6078 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6079 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6081 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6083 // also send plain move, in case ICS does not understand atomic claims
6084 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6088 if (first.sendTime && (gameMode == BeginningOfGame ||
6089 gameMode == MachinePlaysWhite ||
6090 gameMode == MachinePlaysBlack)) {
6091 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6093 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6094 // [HGM] book: if program might be playing, let it use book
6095 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6096 first.maybeThinking = TRUE;
6097 } else SendMoveToProgram(forwardMostMove-1, &first);
6098 if (currentMove == cmailOldMove + 1) {
6099 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6103 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6113 if (WhiteOnMove(currentMove)) {
6114 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6116 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6120 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6125 case MachinePlaysBlack:
6126 case MachinePlaysWhite:
6127 /* disable certain menu options while machine is thinking */
6128 SetMachineThinkingEnables();
6135 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6137 if(bookHit) { // [HGM] book: simulate book reply
6138 static char bookMove[MSG_SIZ]; // a bit generous?
6140 programStats.nodes = programStats.depth = programStats.time =
6141 programStats.score = programStats.got_only_move = 0;
6142 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6144 strcpy(bookMove, "move ");
6145 strcat(bookMove, bookHit);
6146 HandleMachineMove(bookMove, &first);
6152 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6153 int fromX, fromY, toX, toY;
6156 /* [HGM] This routine was added to allow calling of its two logical
6157 parts from other modules in the old way. Before, UserMoveEvent()
6158 automatically called FinishMove() if the move was OK, and returned
6159 otherwise. I separated the two, in order to make it possible to
6160 slip a promotion popup in between. But that it always needs two
6161 calls, to the first part, (now called UserMoveTest() ), and to
6162 FinishMove if the first part succeeded. Calls that do not need
6163 to do anything in between, can call this routine the old way.
6165 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6166 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6167 if(moveType == AmbiguousMove)
6168 DrawPosition(FALSE, boards[currentMove]);
6169 else if(moveType != ImpossibleMove && moveType != Comment)
6170 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6174 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6181 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6182 Markers *m = (Markers *) closure;
6183 if(rf == fromY && ff == fromX)
6184 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6185 || kind == WhiteCapturesEnPassant
6186 || kind == BlackCapturesEnPassant);
6187 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6191 MarkTargetSquares(int clear)
6194 if(!appData.markers || !appData.highlightDragging ||
6195 !appData.testLegality || gameMode == EditPosition) return;
6197 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6200 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6201 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6202 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6204 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6207 DrawPosition(TRUE, NULL);
6210 void LeftClick(ClickType clickType, int xPix, int yPix)
6213 Boolean saveAnimate;
6214 static int second = 0, promotionChoice = 0;
6215 char promoChoice = NULLCHAR;
6217 if(appData.seekGraph && appData.icsActive && loggedOn &&
6218 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6219 SeekGraphClick(clickType, xPix, yPix, 0);
6223 if (clickType == Press) ErrorPopDown();
6224 MarkTargetSquares(1);
6226 x = EventToSquare(xPix, BOARD_WIDTH);
6227 y = EventToSquare(yPix, BOARD_HEIGHT);
6228 if (!flipView && y >= 0) {
6229 y = BOARD_HEIGHT - 1 - y;
6231 if (flipView && x >= 0) {
6232 x = BOARD_WIDTH - 1 - x;
6235 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6236 if(clickType == Release) return; // ignore upclick of click-click destination
6237 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6238 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6239 if(gameInfo.holdingsWidth &&
6240 (WhiteOnMove(currentMove)
6241 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6242 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6243 // click in right holdings, for determining promotion piece
6244 ChessSquare p = boards[currentMove][y][x];
6245 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6246 if(p != EmptySquare) {
6247 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6252 DrawPosition(FALSE, boards[currentMove]);
6256 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6257 if(clickType == Press
6258 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6259 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6260 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6263 autoQueen = appData.alwaysPromoteToQueen;
6266 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6267 if (clickType == Press) {
6269 if (OKToStartUserMove(x, y)) {
6273 MarkTargetSquares(0);
6274 DragPieceBegin(xPix, yPix);
6275 if (appData.highlightDragging) {
6276 SetHighlights(x, y, -1, -1);
6285 if (clickType == Press && gameMode != EditPosition) {
6290 // ignore off-board to clicks
6291 if(y < 0 || x < 0) return;
6293 /* Check if clicking again on the same color piece */
6294 fromP = boards[currentMove][fromY][fromX];
6295 toP = boards[currentMove][y][x];
6296 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6297 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6298 WhitePawn <= toP && toP <= WhiteKing &&
6299 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6300 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6301 (BlackPawn <= fromP && fromP <= BlackKing &&
6302 BlackPawn <= toP && toP <= BlackKing &&
6303 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6304 !(fromP == BlackKing && toP == BlackRook && frc))) {
6305 /* Clicked again on same color piece -- changed his mind */
6306 second = (x == fromX && y == fromY);
6307 if(!second || !OnlyMove(&x, &y, TRUE)) {
6308 if (appData.highlightDragging) {
6309 SetHighlights(x, y, -1, -1);
6313 if (OKToStartUserMove(x, y)) {
6316 MarkTargetSquares(0);
6317 DragPieceBegin(xPix, yPix);
6322 // ignore clicks on holdings
6323 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6326 if (clickType == Release && x == fromX && y == fromY) {
6327 DragPieceEnd(xPix, yPix);
6328 if (appData.animateDragging) {
6329 /* Undo animation damage if any */
6330 DrawPosition(FALSE, NULL);
6333 /* Second up/down in same square; just abort move */
6338 ClearPremoveHighlights();
6340 /* First upclick in same square; start click-click mode */
6341 SetHighlights(x, y, -1, -1);
6346 /* we now have a different from- and (possibly off-board) to-square */
6347 /* Completed move */
6350 saveAnimate = appData.animate;
6351 if (clickType == Press) {
6352 /* Finish clickclick move */
6353 if (appData.animate || appData.highlightLastMove) {
6354 SetHighlights(fromX, fromY, toX, toY);
6359 /* Finish drag move */
6360 if (appData.highlightLastMove) {
6361 SetHighlights(fromX, fromY, toX, toY);
6365 DragPieceEnd(xPix, yPix);
6366 /* Don't animate move and drag both */
6367 appData.animate = FALSE;
6370 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6371 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6372 ChessSquare piece = boards[currentMove][fromY][fromX];
6373 if(gameMode == EditPosition && piece != EmptySquare &&
6374 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6377 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6378 n = PieceToNumber(piece - (int)BlackPawn);
6379 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6380 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6381 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6383 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6384 n = PieceToNumber(piece);
6385 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6386 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6387 boards[currentMove][n][BOARD_WIDTH-2]++;
6389 boards[currentMove][fromY][fromX] = EmptySquare;
6393 DrawPosition(TRUE, boards[currentMove]);
6397 // off-board moves should not be highlighted
6398 if(x < 0 || x < 0) ClearHighlights();
6400 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6401 SetHighlights(fromX, fromY, toX, toY);
6402 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6403 // [HGM] super: promotion to captured piece selected from holdings
6404 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6405 promotionChoice = TRUE;
6406 // kludge follows to temporarily execute move on display, without promoting yet
6407 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6408 boards[currentMove][toY][toX] = p;
6409 DrawPosition(FALSE, boards[currentMove]);
6410 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6411 boards[currentMove][toY][toX] = q;
6412 DisplayMessage("Click in holdings to choose piece", "");
6417 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6418 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6419 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6422 appData.animate = saveAnimate;
6423 if (appData.animate || appData.animateDragging) {
6424 /* Undo animation damage if needed */
6425 DrawPosition(FALSE, NULL);
6429 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6430 { // front-end-free part taken out of PieceMenuPopup
6431 int whichMenu; int xSqr, ySqr;
6433 if(seekGraphUp) { // [HGM] seekgraph
6434 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6435 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6439 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6440 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6441 if(action == Press) {
6442 originalFlip = flipView;
6443 flipView = !flipView; // temporarily flip board to see game from partners perspective
6444 DrawPosition(TRUE, partnerBoard);
6445 DisplayMessage(partnerStatus, "");
6447 } else if(action == Release) {
6448 flipView = originalFlip;
6449 DrawPosition(TRUE, boards[currentMove]);
6455 xSqr = EventToSquare(x, BOARD_WIDTH);
6456 ySqr = EventToSquare(y, BOARD_HEIGHT);
6457 if (action == Release) UnLoadPV(); // [HGM] pv
6458 if (action != Press) return -2; // return code to be ignored
6461 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6463 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6464 if (xSqr < 0 || ySqr < 0) return -1;
\r
6465 whichMenu = 0; // edit-position menu
6468 if(!appData.icsEngineAnalyze) return -1;
6469 case IcsPlayingWhite:
6470 case IcsPlayingBlack:
6471 if(!appData.zippyPlay) goto noZip;
6474 case MachinePlaysWhite:
6475 case MachinePlaysBlack:
6476 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6477 if (!appData.dropMenu) {
6479 return 2; // flag front-end to grab mouse events
6481 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6482 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6485 if (xSqr < 0 || ySqr < 0) return -1;
6486 if (!appData.dropMenu || appData.testLegality &&
6487 gameInfo.variant != VariantBughouse &&
6488 gameInfo.variant != VariantCrazyhouse) return -1;
6489 whichMenu = 1; // drop menu
6495 if (((*fromX = xSqr) < 0) ||
6496 ((*fromY = ySqr) < 0)) {
6497 *fromX = *fromY = -1;
6501 *fromX = BOARD_WIDTH - 1 - *fromX;
6503 *fromY = BOARD_HEIGHT - 1 - *fromY;
6508 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6510 // char * hint = lastHint;
6511 FrontEndProgramStats stats;
6513 stats.which = cps == &first ? 0 : 1;
6514 stats.depth = cpstats->depth;
6515 stats.nodes = cpstats->nodes;
6516 stats.score = cpstats->score;
6517 stats.time = cpstats->time;
6518 stats.pv = cpstats->movelist;
6519 stats.hint = lastHint;
6520 stats.an_move_index = 0;
6521 stats.an_move_count = 0;
6523 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6524 stats.hint = cpstats->move_name;
6525 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6526 stats.an_move_count = cpstats->nr_moves;
6529 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6531 SetProgramStats( &stats );
6535 Adjudicate(ChessProgramState *cps)
6536 { // [HGM] some adjudications useful with buggy engines
6537 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6538 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6539 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6540 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6541 int k, count = 0; static int bare = 1;
6542 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6543 Boolean canAdjudicate = !appData.icsActive;
6545 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6546 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6547 if( appData.testLegality )
6548 { /* [HGM] Some more adjudications for obstinate engines */
6549 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6550 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6551 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6552 static int moveCount = 6;
6554 char *reason = NULL;
6556 /* Count what is on board. */
6557 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6558 { ChessSquare p = boards[forwardMostMove][i][j];
6562 { /* count B,N,R and other of each side */
6565 NrK++; break; // [HGM] atomic: count Kings
6569 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6570 bishopsColor |= 1 << ((i^j)&1);
6575 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6576 bishopsColor |= 1 << ((i^j)&1);
6591 PawnAdvance += m; NrPawns++;
6593 NrPieces += (p != EmptySquare);
6594 NrW += ((int)p < (int)BlackPawn);
6595 if(gameInfo.variant == VariantXiangqi &&
6596 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6597 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6598 NrW -= ((int)p < (int)BlackPawn);
6602 /* Some material-based adjudications that have to be made before stalemate test */
6603 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6604 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6605 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6606 if(canAdjudicate && appData.checkMates) {
6608 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6609 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6610 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6611 "Xboard adjudication: King destroyed", GE_XBOARD );
6616 /* Bare King in Shatranj (loses) or Losers (wins) */
6617 if( NrW == 1 || NrPieces - NrW == 1) {
6618 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6619 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6620 if(canAdjudicate && appData.checkMates) {
6622 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6623 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6625 "Xboard adjudication: Bare king", GE_XBOARD );
6629 if( gameInfo.variant == VariantShatranj && --bare < 0)
6631 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6632 if(canAdjudicate && appData.checkMates) {
6633 /* but only adjudicate if adjudication enabled */
6635 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6636 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6637 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6638 "Xboard adjudication: Bare king", GE_XBOARD );
6645 // don't wait for engine to announce game end if we can judge ourselves
6646 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6648 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6649 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6650 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6651 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6654 reason = "Xboard adjudication: 3rd check";
6655 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6665 reason = "Xboard adjudication: Stalemate";
6666 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6667 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6668 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6669 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6670 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6671 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6672 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6673 EP_CHECKMATE : EP_WINS);
6674 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6675 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6679 reason = "Xboard adjudication: Checkmate";
6680 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6684 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6686 result = GameIsDrawn; break;
6688 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6690 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6692 result = (ChessMove) 0;
6694 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6696 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6697 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6698 GameEnds( result, reason, GE_XBOARD );
6702 /* Next absolutely insufficient mating material. */
6703 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6704 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6705 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6706 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6707 { /* KBK, KNK, KK of KBKB with like Bishops */
6709 /* always flag draws, for judging claims */
6710 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6712 if(canAdjudicate && appData.materialDraws) {
6713 /* but only adjudicate them if adjudication enabled */
6714 if(engineOpponent) {
6715 SendToProgram("force\n", engineOpponent); // suppress reply
6716 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6718 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6719 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6724 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6726 ( NrWR == 1 && NrBR == 1 /* KRKR */
6727 || NrWQ==1 && NrBQ==1 /* KQKQ */
6728 || NrWN==2 || NrBN==2 /* KNNK */
6729 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6731 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6732 { /* if the first 3 moves do not show a tactical win, declare draw */
6733 if(engineOpponent) {
6734 SendToProgram("force\n", engineOpponent); // suppress reply
6735 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6737 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6738 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6741 } else moveCount = 6;
6745 if (appData.debugMode) { int i;
6746 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6747 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6748 appData.drawRepeats);
6749 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6750 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6754 // Repetition draws and 50-move rule can be applied independently of legality testing
6756 /* Check for rep-draws */
6758 for(k = forwardMostMove-2;
6759 k>=backwardMostMove && k>=forwardMostMove-100 &&
6760 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6761 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6764 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6765 /* compare castling rights */
6766 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6767 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6768 rights++; /* King lost rights, while rook still had them */
6769 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6770 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6771 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6772 rights++; /* but at least one rook lost them */
6774 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6775 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6777 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6778 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6779 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6782 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6783 && appData.drawRepeats > 1) {
6784 /* adjudicate after user-specified nr of repeats */
6785 if(engineOpponent) {
6786 SendToProgram("force\n", engineOpponent); // suppress reply
6787 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6789 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6790 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6791 // [HGM] xiangqi: check for forbidden perpetuals
6792 int m, ourPerpetual = 1, hisPerpetual = 1;
6793 for(m=forwardMostMove; m>k; m-=2) {
6794 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6795 ourPerpetual = 0; // the current mover did not always check
6796 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6797 hisPerpetual = 0; // the opponent did not always check
6799 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6800 ourPerpetual, hisPerpetual);
6801 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6802 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6803 "Xboard adjudication: perpetual checking", GE_XBOARD );
6806 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6807 break; // (or we would have caught him before). Abort repetition-checking loop.
6808 // Now check for perpetual chases
6809 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6810 hisPerpetual = PerpetualChase(k, forwardMostMove);
6811 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6812 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6813 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6814 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6817 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6818 break; // Abort repetition-checking loop.
6820 // if neither of us is checking or chasing all the time, or both are, it is draw
6822 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6825 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6826 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6830 /* Now we test for 50-move draws. Determine ply count */
6831 count = forwardMostMove;
6832 /* look for last irreversble move */
6833 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6835 /* if we hit starting position, add initial plies */
6836 if( count == backwardMostMove )
6837 count -= initialRulePlies;
6838 count = forwardMostMove - count;
6840 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6841 /* this is used to judge if draw claims are legal */
6842 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6843 if(engineOpponent) {
6844 SendToProgram("force\n", engineOpponent); // suppress reply
6845 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6847 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6848 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6852 /* if draw offer is pending, treat it as a draw claim
6853 * when draw condition present, to allow engines a way to
6854 * claim draws before making their move to avoid a race
6855 * condition occurring after their move
6857 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6859 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6860 p = "Draw claim: 50-move rule";
6861 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6862 p = "Draw claim: 3-fold repetition";
6863 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6864 p = "Draw claim: insufficient mating material";
6865 if( p != NULL && canAdjudicate) {
6866 if(engineOpponent) {
6867 SendToProgram("force\n", engineOpponent); // suppress reply
6868 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6871 GameEnds( GameIsDrawn, p, GE_XBOARD );
6876 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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: long game", GE_XBOARD );
6888 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6889 { // [HGM] book: this routine intercepts moves to simulate book replies
6890 char *bookHit = NULL;
6892 //first determine if the incoming move brings opponent into his book
6893 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6894 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6895 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6896 if(bookHit != NULL && !cps->bookSuspend) {
6897 // make sure opponent is not going to reply after receiving move to book position
6898 SendToProgram("force\n", cps);
6899 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6901 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6902 // now arrange restart after book miss
6904 // after a book hit we never send 'go', and the code after the call to this routine
6905 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6907 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6908 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6909 SendToProgram(buf, cps);
6910 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6911 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6912 SendToProgram("go\n", cps);
6913 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6914 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6915 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6916 SendToProgram("go\n", cps);
6917 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6919 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6923 ChessProgramState *savedState;
6924 void DeferredBookMove(void)
6926 if(savedState->lastPing != savedState->lastPong)
6927 ScheduleDelayedEvent(DeferredBookMove, 10);
6929 HandleMachineMove(savedMessage, savedState);
6933 HandleMachineMove(message, cps)
6935 ChessProgramState *cps;
6937 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6938 char realname[MSG_SIZ];
6939 int fromX, fromY, toX, toY;
6948 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6950 * Kludge to ignore BEL characters
6952 while (*message == '\007') message++;
6955 * [HGM] engine debug message: ignore lines starting with '#' character
6957 if(cps->debug && *message == '#') return;
6960 * Look for book output
6962 if (cps == &first && bookRequested) {
6963 if (message[0] == '\t' || message[0] == ' ') {
6964 /* Part of the book output is here; append it */
6965 strcat(bookOutput, message);
6966 strcat(bookOutput, " \n");
6968 } else if (bookOutput[0] != NULLCHAR) {
6969 /* All of book output has arrived; display it */
6970 char *p = bookOutput;
6971 while (*p != NULLCHAR) {
6972 if (*p == '\t') *p = ' ';
6975 DisplayInformation(bookOutput);
6976 bookRequested = FALSE;
6977 /* Fall through to parse the current output */
6982 * Look for machine move.
6984 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6985 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6987 /* This method is only useful on engines that support ping */
6988 if (cps->lastPing != cps->lastPong) {
6989 if (gameMode == BeginningOfGame) {
6990 /* Extra move from before last new; ignore */
6991 if (appData.debugMode) {
6992 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6995 if (appData.debugMode) {
6996 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6997 cps->which, gameMode);
7000 SendToProgram("undo\n", cps);
7006 case BeginningOfGame:
7007 /* Extra move from before last reset; ignore */
7008 if (appData.debugMode) {
7009 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7016 /* Extra move after we tried to stop. The mode test is
7017 not a reliable way of detecting this problem, but it's
7018 the best we can do on engines that don't support ping.
7020 if (appData.debugMode) {
7021 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7022 cps->which, gameMode);
7024 SendToProgram("undo\n", cps);
7027 case MachinePlaysWhite:
7028 case IcsPlayingWhite:
7029 machineWhite = TRUE;
7032 case MachinePlaysBlack:
7033 case IcsPlayingBlack:
7034 machineWhite = FALSE;
7037 case TwoMachinesPlay:
7038 machineWhite = (cps->twoMachinesColor[0] == 'w');
7041 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7042 if (appData.debugMode) {
7044 "Ignoring move out of turn by %s, gameMode %d"
7045 ", forwardMost %d\n",
7046 cps->which, gameMode, forwardMostMove);
7051 if (appData.debugMode) { int f = forwardMostMove;
7052 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7053 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7054 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7056 if(cps->alphaRank) AlphaRank(machineMove, 4);
7057 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7058 &fromX, &fromY, &toX, &toY, &promoChar)) {
7059 /* Machine move could not be parsed; ignore it. */
7060 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7061 machineMove, cps->which);
7062 DisplayError(buf1, 0);
7063 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7064 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7065 if (gameMode == TwoMachinesPlay) {
7066 GameEnds(machineWhite ? BlackWins : WhiteWins,
7072 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7073 /* So we have to redo legality test with true e.p. status here, */
7074 /* to make sure an illegal e.p. capture does not slip through, */
7075 /* to cause a forfeit on a justified illegal-move complaint */
7076 /* of the opponent. */
7077 if( gameMode==TwoMachinesPlay && appData.testLegality
7078 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7081 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7082 fromY, fromX, toY, toX, promoChar);
7083 if (appData.debugMode) {
7085 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7086 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7087 fprintf(debugFP, "castling rights\n");
7089 if(moveType == IllegalMove) {
7090 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7091 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7092 GameEnds(machineWhite ? BlackWins : WhiteWins,
7095 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7096 /* [HGM] Kludge to handle engines that send FRC-style castling
7097 when they shouldn't (like TSCP-Gothic) */
7099 case WhiteASideCastleFR:
7100 case BlackASideCastleFR:
7102 currentMoveString[2]++;
7104 case WhiteHSideCastleFR:
7105 case BlackHSideCastleFR:
7107 currentMoveString[2]--;
7109 default: ; // nothing to do, but suppresses warning of pedantic compilers
7112 hintRequested = FALSE;
7113 lastHint[0] = NULLCHAR;
7114 bookRequested = FALSE;
7115 /* Program may be pondering now */
7116 cps->maybeThinking = TRUE;
7117 if (cps->sendTime == 2) cps->sendTime = 1;
7118 if (cps->offeredDraw) cps->offeredDraw--;
7120 /* currentMoveString is set as a side-effect of ParseOneMove */
7121 strcpy(machineMove, currentMoveString);
7122 strcat(machineMove, "\n");
7123 strcpy(moveList[forwardMostMove], machineMove);
7125 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7127 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7128 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7131 while( count < adjudicateLossPlies ) {
7132 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7135 score = -score; /* Flip score for winning side */
7138 if( score > adjudicateLossThreshold ) {
7145 if( count >= adjudicateLossPlies ) {
7146 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7148 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7149 "Xboard adjudication",
7156 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7159 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7161 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7162 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7164 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7166 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7168 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7169 char buf[3*MSG_SIZ];
7171 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7172 programStats.score / 100.,
7174 programStats.time / 100.,
7175 (unsigned int)programStats.nodes,
7176 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7177 programStats.movelist);
7179 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7184 /* [AS] Save move info and clear stats for next move */
7185 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7186 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7187 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7188 ClearProgramStats();
7189 thinkOutput[0] = NULLCHAR;
7190 hiddenThinkOutputState = 0;
7193 if (gameMode == TwoMachinesPlay) {
7194 /* [HGM] relaying draw offers moved to after reception of move */
7195 /* and interpreting offer as claim if it brings draw condition */
7196 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7197 SendToProgram("draw\n", cps->other);
7199 if (cps->other->sendTime) {
7200 SendTimeRemaining(cps->other,
7201 cps->other->twoMachinesColor[0] == 'w');
7203 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7204 if (firstMove && !bookHit) {
7206 if (cps->other->useColors) {
7207 SendToProgram(cps->other->twoMachinesColor, cps->other);
7209 SendToProgram("go\n", cps->other);
7211 cps->other->maybeThinking = TRUE;
7214 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7216 if (!pausing && appData.ringBellAfterMoves) {
7221 * Reenable menu items that were disabled while
7222 * machine was thinking
7224 if (gameMode != TwoMachinesPlay)
7225 SetUserThinkingEnables();
7227 // [HGM] book: after book hit opponent has received move and is now in force mode
7228 // force the book reply into it, and then fake that it outputted this move by jumping
7229 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7231 static char bookMove[MSG_SIZ]; // a bit generous?
7233 strcpy(bookMove, "move ");
7234 strcat(bookMove, bookHit);
7237 programStats.nodes = programStats.depth = programStats.time =
7238 programStats.score = programStats.got_only_move = 0;
7239 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7241 if(cps->lastPing != cps->lastPong) {
7242 savedMessage = message; // args for deferred call
7244 ScheduleDelayedEvent(DeferredBookMove, 10);
7253 /* Set special modes for chess engines. Later something general
7254 * could be added here; for now there is just one kludge feature,
7255 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7256 * when "xboard" is given as an interactive command.
7258 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7259 cps->useSigint = FALSE;
7260 cps->useSigterm = FALSE;
7262 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7263 ParseFeatures(message+8, cps);
7264 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7267 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7268 * want this, I was asked to put it in, and obliged.
7270 if (!strncmp(message, "setboard ", 9)) {
7271 Board initial_position;
7273 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7275 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7276 DisplayError(_("Bad FEN received from engine"), 0);
7280 CopyBoard(boards[0], initial_position);
7281 initialRulePlies = FENrulePlies;
7282 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7283 else gameMode = MachinePlaysBlack;
7284 DrawPosition(FALSE, boards[currentMove]);
7290 * Look for communication commands
7292 if (!strncmp(message, "telluser ", 9)) {
7293 DisplayNote(message + 9);
7296 if (!strncmp(message, "tellusererror ", 14)) {
7298 DisplayError(message + 14, 0);
7301 if (!strncmp(message, "tellopponent ", 13)) {
7302 if (appData.icsActive) {
7304 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7308 DisplayNote(message + 13);
7312 if (!strncmp(message, "tellothers ", 11)) {
7313 if (appData.icsActive) {
7315 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7321 if (!strncmp(message, "tellall ", 8)) {
7322 if (appData.icsActive) {
7324 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7328 DisplayNote(message + 8);
7332 if (strncmp(message, "warning", 7) == 0) {
7333 /* Undocumented feature, use tellusererror in new code */
7334 DisplayError(message, 0);
7337 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7338 strcpy(realname, cps->tidy);
7339 strcat(realname, " query");
7340 AskQuestion(realname, buf2, buf1, cps->pr);
7343 /* Commands from the engine directly to ICS. We don't allow these to be
7344 * sent until we are logged on. Crafty kibitzes have been known to
7345 * interfere with the login process.
7348 if (!strncmp(message, "tellics ", 8)) {
7349 SendToICS(message + 8);
7353 if (!strncmp(message, "tellicsnoalias ", 15)) {
7354 SendToICS(ics_prefix);
7355 SendToICS(message + 15);
7359 /* The following are for backward compatibility only */
7360 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7361 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7362 SendToICS(ics_prefix);
7368 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7372 * If the move is illegal, cancel it and redraw the board.
7373 * Also deal with other error cases. Matching is rather loose
7374 * here to accommodate engines written before the spec.
7376 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7377 strncmp(message, "Error", 5) == 0) {
7378 if (StrStr(message, "name") ||
7379 StrStr(message, "rating") || StrStr(message, "?") ||
7380 StrStr(message, "result") || StrStr(message, "board") ||
7381 StrStr(message, "bk") || StrStr(message, "computer") ||
7382 StrStr(message, "variant") || StrStr(message, "hint") ||
7383 StrStr(message, "random") || StrStr(message, "depth") ||
7384 StrStr(message, "accepted")) {
7387 if (StrStr(message, "protover")) {
7388 /* Program is responding to input, so it's apparently done
7389 initializing, and this error message indicates it is
7390 protocol version 1. So we don't need to wait any longer
7391 for it to initialize and send feature commands. */
7392 FeatureDone(cps, 1);
7393 cps->protocolVersion = 1;
7396 cps->maybeThinking = FALSE;
7398 if (StrStr(message, "draw")) {
7399 /* Program doesn't have "draw" command */
7400 cps->sendDrawOffers = 0;
7403 if (cps->sendTime != 1 &&
7404 (StrStr(message, "time") || StrStr(message, "otim"))) {
7405 /* Program apparently doesn't have "time" or "otim" command */
7409 if (StrStr(message, "analyze")) {
7410 cps->analysisSupport = FALSE;
7411 cps->analyzing = FALSE;
7413 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7414 DisplayError(buf2, 0);
7417 if (StrStr(message, "(no matching move)st")) {
7418 /* Special kludge for GNU Chess 4 only */
7419 cps->stKludge = TRUE;
7420 SendTimeControl(cps, movesPerSession, timeControl,
7421 timeIncrement, appData.searchDepth,
7425 if (StrStr(message, "(no matching move)sd")) {
7426 /* Special kludge for GNU Chess 4 only */
7427 cps->sdKludge = TRUE;
7428 SendTimeControl(cps, movesPerSession, timeControl,
7429 timeIncrement, appData.searchDepth,
7433 if (!StrStr(message, "llegal")) {
7436 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7437 gameMode == IcsIdle) return;
7438 if (forwardMostMove <= backwardMostMove) return;
7439 if (pausing) PauseEvent();
7440 if(appData.forceIllegal) {
7441 // [HGM] illegal: machine refused move; force position after move into it
7442 SendToProgram("force\n", cps);
7443 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7444 // we have a real problem now, as SendBoard will use the a2a3 kludge
7445 // when black is to move, while there might be nothing on a2 or black
7446 // might already have the move. So send the board as if white has the move.
7447 // But first we must change the stm of the engine, as it refused the last move
7448 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7449 if(WhiteOnMove(forwardMostMove)) {
7450 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7451 SendBoard(cps, forwardMostMove); // kludgeless board
7453 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7454 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7455 SendBoard(cps, forwardMostMove+1); // kludgeless board
7457 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7458 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7459 gameMode == TwoMachinesPlay)
7460 SendToProgram("go\n", cps);
7463 if (gameMode == PlayFromGameFile) {
7464 /* Stop reading this game file */
7465 gameMode = EditGame;
7468 currentMove = forwardMostMove-1;
7469 DisplayMove(currentMove-1); /* before DisplayMoveError */
7470 SwitchClocks(forwardMostMove-1); // [HGM] race
7471 DisplayBothClocks();
7472 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7473 parseList[currentMove], cps->which);
7474 DisplayMoveError(buf1);
7475 DrawPosition(FALSE, boards[currentMove]);
7477 /* [HGM] illegal-move claim should forfeit game when Xboard */
7478 /* only passes fully legal moves */
7479 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7480 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7481 "False illegal-move claim", GE_XBOARD );
7485 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7486 /* Program has a broken "time" command that
7487 outputs a string not ending in newline.
7493 * If chess program startup fails, exit with an error message.
7494 * Attempts to recover here are futile.
7496 if ((StrStr(message, "unknown host") != NULL)
7497 || (StrStr(message, "No remote directory") != NULL)
7498 || (StrStr(message, "not found") != NULL)
7499 || (StrStr(message, "No such file") != NULL)
7500 || (StrStr(message, "can't alloc") != NULL)
7501 || (StrStr(message, "Permission denied") != NULL)) {
7503 cps->maybeThinking = FALSE;
7504 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7505 cps->which, cps->program, cps->host, message);
7506 RemoveInputSource(cps->isr);
7507 DisplayFatalError(buf1, 0, 1);
7512 * Look for hint output
7514 if (sscanf(message, "Hint: %s", buf1) == 1) {
7515 if (cps == &first && hintRequested) {
7516 hintRequested = FALSE;
7517 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7518 &fromX, &fromY, &toX, &toY, &promoChar)) {
7519 (void) CoordsToAlgebraic(boards[forwardMostMove],
7520 PosFlags(forwardMostMove),
7521 fromY, fromX, toY, toX, promoChar, buf1);
7522 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7523 DisplayInformation(buf2);
7525 /* Hint move could not be parsed!? */
7526 snprintf(buf2, sizeof(buf2),
7527 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7529 DisplayError(buf2, 0);
7532 strcpy(lastHint, buf1);
7538 * Ignore other messages if game is not in progress
7540 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7541 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7544 * look for win, lose, draw, or draw offer
7546 if (strncmp(message, "1-0", 3) == 0) {
7547 char *p, *q, *r = "";
7548 p = strchr(message, '{');
7556 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7558 } else if (strncmp(message, "0-1", 3) == 0) {
7559 char *p, *q, *r = "";
7560 p = strchr(message, '{');
7568 /* Kludge for Arasan 4.1 bug */
7569 if (strcmp(r, "Black resigns") == 0) {
7570 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7573 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7575 } else if (strncmp(message, "1/2", 3) == 0) {
7576 char *p, *q, *r = "";
7577 p = strchr(message, '{');
7586 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7589 } else if (strncmp(message, "White resign", 12) == 0) {
7590 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7592 } else if (strncmp(message, "Black resign", 12) == 0) {
7593 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7595 } else if (strncmp(message, "White matches", 13) == 0 ||
7596 strncmp(message, "Black matches", 13) == 0 ) {
7597 /* [HGM] ignore GNUShogi noises */
7599 } else if (strncmp(message, "White", 5) == 0 &&
7600 message[5] != '(' &&
7601 StrStr(message, "Black") == NULL) {
7602 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7604 } else if (strncmp(message, "Black", 5) == 0 &&
7605 message[5] != '(') {
7606 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7608 } else if (strcmp(message, "resign") == 0 ||
7609 strcmp(message, "computer resigns") == 0) {
7611 case MachinePlaysBlack:
7612 case IcsPlayingBlack:
7613 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7615 case MachinePlaysWhite:
7616 case IcsPlayingWhite:
7617 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7619 case TwoMachinesPlay:
7620 if (cps->twoMachinesColor[0] == 'w')
7621 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7623 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7630 } else if (strncmp(message, "opponent mates", 14) == 0) {
7632 case MachinePlaysBlack:
7633 case IcsPlayingBlack:
7634 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7636 case MachinePlaysWhite:
7637 case IcsPlayingWhite:
7638 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7640 case TwoMachinesPlay:
7641 if (cps->twoMachinesColor[0] == 'w')
7642 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7644 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7651 } else if (strncmp(message, "computer mates", 14) == 0) {
7653 case MachinePlaysBlack:
7654 case IcsPlayingBlack:
7655 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7657 case MachinePlaysWhite:
7658 case IcsPlayingWhite:
7659 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7661 case TwoMachinesPlay:
7662 if (cps->twoMachinesColor[0] == 'w')
7663 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7665 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7672 } else if (strncmp(message, "checkmate", 9) == 0) {
7673 if (WhiteOnMove(forwardMostMove)) {
7674 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7676 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7679 } else if (strstr(message, "Draw") != NULL ||
7680 strstr(message, "game is a draw") != NULL) {
7681 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7683 } else if (strstr(message, "offer") != NULL &&
7684 strstr(message, "draw") != NULL) {
7686 if (appData.zippyPlay && first.initDone) {
7687 /* Relay offer to ICS */
7688 SendToICS(ics_prefix);
7689 SendToICS("draw\n");
7692 cps->offeredDraw = 2; /* valid until this engine moves twice */
7693 if (gameMode == TwoMachinesPlay) {
7694 if (cps->other->offeredDraw) {
7695 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7696 /* [HGM] in two-machine mode we delay relaying draw offer */
7697 /* until after we also have move, to see if it is really claim */
7699 } else if (gameMode == MachinePlaysWhite ||
7700 gameMode == MachinePlaysBlack) {
7701 if (userOfferedDraw) {
7702 DisplayInformation(_("Machine accepts your draw offer"));
7703 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7705 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7712 * Look for thinking output
7714 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7715 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7717 int plylev, mvleft, mvtot, curscore, time;
7718 char mvname[MOVE_LEN];
7722 int prefixHint = FALSE;
7723 mvname[0] = NULLCHAR;
7726 case MachinePlaysBlack:
7727 case IcsPlayingBlack:
7728 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7730 case MachinePlaysWhite:
7731 case IcsPlayingWhite:
7732 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7737 case IcsObserving: /* [DM] icsEngineAnalyze */
7738 if (!appData.icsEngineAnalyze) ignore = TRUE;
7740 case TwoMachinesPlay:
7741 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7752 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7753 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7755 if (plyext != ' ' && plyext != '\t') {
7759 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7760 if( cps->scoreIsAbsolute &&
7761 ( gameMode == MachinePlaysBlack ||
7762 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7763 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7764 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7765 !WhiteOnMove(currentMove)
7768 curscore = -curscore;
7772 programStats.depth = plylev;
7773 programStats.nodes = nodes;
7774 programStats.time = time;
7775 programStats.score = curscore;
7776 programStats.got_only_move = 0;
7778 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7781 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7782 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7783 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7784 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7785 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7786 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7787 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7788 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7791 /* Buffer overflow protection */
7792 if (buf1[0] != NULLCHAR) {
7793 if (strlen(buf1) >= sizeof(programStats.movelist)
7794 && appData.debugMode) {
7796 "PV is too long; using the first %u bytes.\n",
7797 (unsigned) sizeof(programStats.movelist) - 1);
7800 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7802 sprintf(programStats.movelist, " no PV\n");
7805 if (programStats.seen_stat) {
7806 programStats.ok_to_send = 1;
7809 if (strchr(programStats.movelist, '(') != NULL) {
7810 programStats.line_is_book = 1;
7811 programStats.nr_moves = 0;
7812 programStats.moves_left = 0;
7814 programStats.line_is_book = 0;
7817 SendProgramStatsToFrontend( cps, &programStats );
7820 [AS] Protect the thinkOutput buffer from overflow... this
7821 is only useful if buf1 hasn't overflowed first!
7823 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7825 (gameMode == TwoMachinesPlay ?
7826 ToUpper(cps->twoMachinesColor[0]) : ' '),
7827 ((double) curscore) / 100.0,
7828 prefixHint ? lastHint : "",
7829 prefixHint ? " " : "" );
7831 if( buf1[0] != NULLCHAR ) {
7832 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7834 if( strlen(buf1) > max_len ) {
7835 if( appData.debugMode) {
7836 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7838 buf1[max_len+1] = '\0';
7841 strcat( thinkOutput, buf1 );
7844 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7845 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7846 DisplayMove(currentMove - 1);
7850 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7851 /* crafty (9.25+) says "(only move) <move>"
7852 * if there is only 1 legal move
7854 sscanf(p, "(only move) %s", buf1);
7855 sprintf(thinkOutput, "%s (only move)", buf1);
7856 sprintf(programStats.movelist, "%s (only move)", buf1);
7857 programStats.depth = 1;
7858 programStats.nr_moves = 1;
7859 programStats.moves_left = 1;
7860 programStats.nodes = 1;
7861 programStats.time = 1;
7862 programStats.got_only_move = 1;
7864 /* Not really, but we also use this member to
7865 mean "line isn't going to change" (Crafty
7866 isn't searching, so stats won't change) */
7867 programStats.line_is_book = 1;
7869 SendProgramStatsToFrontend( cps, &programStats );
7871 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7872 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7873 DisplayMove(currentMove - 1);
7876 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7877 &time, &nodes, &plylev, &mvleft,
7878 &mvtot, mvname) >= 5) {
7879 /* The stat01: line is from Crafty (9.29+) in response
7880 to the "." command */
7881 programStats.seen_stat = 1;
7882 cps->maybeThinking = TRUE;
7884 if (programStats.got_only_move || !appData.periodicUpdates)
7887 programStats.depth = plylev;
7888 programStats.time = time;
7889 programStats.nodes = nodes;
7890 programStats.moves_left = mvleft;
7891 programStats.nr_moves = mvtot;
7892 strcpy(programStats.move_name, mvname);
7893 programStats.ok_to_send = 1;
7894 programStats.movelist[0] = '\0';
7896 SendProgramStatsToFrontend( cps, &programStats );
7900 } else if (strncmp(message,"++",2) == 0) {
7901 /* Crafty 9.29+ outputs this */
7902 programStats.got_fail = 2;
7905 } else if (strncmp(message,"--",2) == 0) {
7906 /* Crafty 9.29+ outputs this */
7907 programStats.got_fail = 1;
7910 } else if (thinkOutput[0] != NULLCHAR &&
7911 strncmp(message, " ", 4) == 0) {
7912 unsigned message_len;
7915 while (*p && *p == ' ') p++;
7917 message_len = strlen( p );
7919 /* [AS] Avoid buffer overflow */
7920 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7921 strcat(thinkOutput, " ");
7922 strcat(thinkOutput, p);
7925 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7926 strcat(programStats.movelist, " ");
7927 strcat(programStats.movelist, p);
7930 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7931 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7932 DisplayMove(currentMove - 1);
7940 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7941 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7943 ChessProgramStats cpstats;
7945 if (plyext != ' ' && plyext != '\t') {
7949 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7950 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7951 curscore = -curscore;
7954 cpstats.depth = plylev;
7955 cpstats.nodes = nodes;
7956 cpstats.time = time;
7957 cpstats.score = curscore;
7958 cpstats.got_only_move = 0;
7959 cpstats.movelist[0] = '\0';
7961 if (buf1[0] != NULLCHAR) {
7962 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7965 cpstats.ok_to_send = 0;
7966 cpstats.line_is_book = 0;
7967 cpstats.nr_moves = 0;
7968 cpstats.moves_left = 0;
7970 SendProgramStatsToFrontend( cps, &cpstats );
7977 /* Parse a game score from the character string "game", and
7978 record it as the history of the current game. The game
7979 score is NOT assumed to start from the standard position.
7980 The display is not updated in any way.
7983 ParseGameHistory(game)
7987 int fromX, fromY, toX, toY, boardIndex;
7992 if (appData.debugMode)
7993 fprintf(debugFP, "Parsing game history: %s\n", game);
7995 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7996 gameInfo.site = StrSave(appData.icsHost);
7997 gameInfo.date = PGNDate();
7998 gameInfo.round = StrSave("-");
8000 /* Parse out names of players */
8001 while (*game == ' ') game++;
8003 while (*game != ' ') *p++ = *game++;
8005 gameInfo.white = StrSave(buf);
8006 while (*game == ' ') game++;
8008 while (*game != ' ' && *game != '\n') *p++ = *game++;
8010 gameInfo.black = StrSave(buf);
8013 boardIndex = blackPlaysFirst ? 1 : 0;
8016 yyboardindex = boardIndex;
8017 moveType = (ChessMove) yylex();
8019 case IllegalMove: /* maybe suicide chess, etc. */
8020 if (appData.debugMode) {
8021 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8022 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8023 setbuf(debugFP, NULL);
8025 case WhitePromotionChancellor:
8026 case BlackPromotionChancellor:
8027 case WhitePromotionArchbishop:
8028 case BlackPromotionArchbishop:
8029 case WhitePromotionQueen:
8030 case BlackPromotionQueen:
8031 case WhitePromotionRook:
8032 case BlackPromotionRook:
8033 case WhitePromotionBishop:
8034 case BlackPromotionBishop:
8035 case WhitePromotionKnight:
8036 case BlackPromotionKnight:
8037 case WhitePromotionKing:
8038 case BlackPromotionKing:
8040 case WhiteCapturesEnPassant:
8041 case BlackCapturesEnPassant:
8042 case WhiteKingSideCastle:
8043 case WhiteQueenSideCastle:
8044 case BlackKingSideCastle:
8045 case BlackQueenSideCastle:
8046 case WhiteKingSideCastleWild:
8047 case WhiteQueenSideCastleWild:
8048 case BlackKingSideCastleWild:
8049 case BlackQueenSideCastleWild:
8051 case WhiteHSideCastleFR:
8052 case WhiteASideCastleFR:
8053 case BlackHSideCastleFR:
8054 case BlackASideCastleFR:
8056 fromX = currentMoveString[0] - AAA;
8057 fromY = currentMoveString[1] - ONE;
8058 toX = currentMoveString[2] - AAA;
8059 toY = currentMoveString[3] - ONE;
8060 promoChar = currentMoveString[4];
8064 fromX = moveType == WhiteDrop ?
8065 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8066 (int) CharToPiece(ToLower(currentMoveString[0]));
8068 toX = currentMoveString[2] - AAA;
8069 toY = currentMoveString[3] - ONE;
8070 promoChar = NULLCHAR;
8074 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8075 if (appData.debugMode) {
8076 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8077 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8078 setbuf(debugFP, NULL);
8080 DisplayError(buf, 0);
8082 case ImpossibleMove:
8084 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8085 if (appData.debugMode) {
8086 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8087 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8088 setbuf(debugFP, NULL);
8090 DisplayError(buf, 0);
8092 case (ChessMove) 0: /* end of file */
8093 if (boardIndex < backwardMostMove) {
8094 /* Oops, gap. How did that happen? */
8095 DisplayError(_("Gap in move list"), 0);
8098 backwardMostMove = blackPlaysFirst ? 1 : 0;
8099 if (boardIndex > forwardMostMove) {
8100 forwardMostMove = boardIndex;
8104 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8105 strcat(parseList[boardIndex-1], " ");
8106 strcat(parseList[boardIndex-1], yy_text);
8118 case GameUnfinished:
8119 if (gameMode == IcsExamining) {
8120 if (boardIndex < backwardMostMove) {
8121 /* Oops, gap. How did that happen? */
8124 backwardMostMove = blackPlaysFirst ? 1 : 0;
8127 gameInfo.result = moveType;
8128 p = strchr(yy_text, '{');
8129 if (p == NULL) p = strchr(yy_text, '(');
8132 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8134 q = strchr(p, *p == '{' ? '}' : ')');
8135 if (q != NULL) *q = NULLCHAR;
8138 gameInfo.resultDetails = StrSave(p);
8141 if (boardIndex >= forwardMostMove &&
8142 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8143 backwardMostMove = blackPlaysFirst ? 1 : 0;
8146 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8147 fromY, fromX, toY, toX, promoChar,
8148 parseList[boardIndex]);
8149 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8150 /* currentMoveString is set as a side-effect of yylex */
8151 strcpy(moveList[boardIndex], currentMoveString);
8152 strcat(moveList[boardIndex], "\n");
8154 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8155 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8161 if(gameInfo.variant != VariantShogi)
8162 strcat(parseList[boardIndex - 1], "+");
8166 strcat(parseList[boardIndex - 1], "#");
8173 /* Apply a move to the given board */
8175 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8176 int fromX, fromY, toX, toY;
8180 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8181 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8183 /* [HGM] compute & store e.p. status and castling rights for new position */
8184 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8187 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8188 oldEP = (signed char)board[EP_STATUS];
8189 board[EP_STATUS] = EP_NONE;
8191 if( board[toY][toX] != EmptySquare )
8192 board[EP_STATUS] = EP_CAPTURE;
8194 if( board[fromY][fromX] == WhitePawn ) {
8195 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8196 board[EP_STATUS] = EP_PAWN_MOVE;
8198 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8199 gameInfo.variant != VariantBerolina || toX < fromX)
8200 board[EP_STATUS] = toX | berolina;
8201 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8202 gameInfo.variant != VariantBerolina || toX > fromX)
8203 board[EP_STATUS] = toX;
8206 if( board[fromY][fromX] == BlackPawn ) {
8207 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8208 board[EP_STATUS] = EP_PAWN_MOVE;
8209 if( toY-fromY== -2) {
8210 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8211 gameInfo.variant != VariantBerolina || toX < fromX)
8212 board[EP_STATUS] = toX | berolina;
8213 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8214 gameInfo.variant != VariantBerolina || toX > fromX)
8215 board[EP_STATUS] = toX;
8219 for(i=0; i<nrCastlingRights; i++) {
8220 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8221 board[CASTLING][i] == toX && castlingRank[i] == toY
8222 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8227 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8228 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8229 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8231 if (fromX == toX && fromY == toY) return;
8233 if (fromY == DROP_RANK) {
8235 piece = board[toY][toX] = (ChessSquare) fromX;
8237 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8238 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8239 if(gameInfo.variant == VariantKnightmate)
8240 king += (int) WhiteUnicorn - (int) WhiteKing;
8242 /* Code added by Tord: */
8243 /* FRC castling assumed when king captures friendly rook. */
8244 if (board[fromY][fromX] == WhiteKing &&
8245 board[toY][toX] == WhiteRook) {
8246 board[fromY][fromX] = EmptySquare;
8247 board[toY][toX] = EmptySquare;
8249 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8251 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8253 } else if (board[fromY][fromX] == BlackKing &&
8254 board[toY][toX] == BlackRook) {
8255 board[fromY][fromX] = EmptySquare;
8256 board[toY][toX] = EmptySquare;
8258 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8260 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8262 /* End of code added by Tord */
8264 } else if (board[fromY][fromX] == king
8265 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8266 && toY == fromY && toX > fromX+1) {
8267 board[fromY][fromX] = EmptySquare;
8268 board[toY][toX] = king;
8269 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8270 board[fromY][BOARD_RGHT-1] = EmptySquare;
8271 } else if (board[fromY][fromX] == king
8272 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8273 && toY == fromY && toX < fromX-1) {
8274 board[fromY][fromX] = EmptySquare;
8275 board[toY][toX] = king;
8276 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8277 board[fromY][BOARD_LEFT] = EmptySquare;
8278 } else if (board[fromY][fromX] == WhitePawn
8279 && toY >= BOARD_HEIGHT-promoRank
8280 && gameInfo.variant != VariantXiangqi
8282 /* white pawn promotion */
8283 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8284 if (board[toY][toX] == EmptySquare) {
8285 board[toY][toX] = WhiteQueen;
8287 if(gameInfo.variant==VariantBughouse ||
8288 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8289 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8290 board[fromY][fromX] = EmptySquare;
8291 } else if ((fromY == BOARD_HEIGHT-4)
8293 && gameInfo.variant != VariantXiangqi
8294 && gameInfo.variant != VariantBerolina
8295 && (board[fromY][fromX] == WhitePawn)
8296 && (board[toY][toX] == EmptySquare)) {
8297 board[fromY][fromX] = EmptySquare;
8298 board[toY][toX] = WhitePawn;
8299 captured = board[toY - 1][toX];
8300 board[toY - 1][toX] = EmptySquare;
8301 } else if ((fromY == BOARD_HEIGHT-4)
8303 && gameInfo.variant == VariantBerolina
8304 && (board[fromY][fromX] == WhitePawn)
8305 && (board[toY][toX] == EmptySquare)) {
8306 board[fromY][fromX] = EmptySquare;
8307 board[toY][toX] = WhitePawn;
8308 if(oldEP & EP_BEROLIN_A) {
8309 captured = board[fromY][fromX-1];
8310 board[fromY][fromX-1] = EmptySquare;
8311 }else{ captured = board[fromY][fromX+1];
8312 board[fromY][fromX+1] = EmptySquare;
8314 } else if (board[fromY][fromX] == king
8315 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8316 && toY == fromY && toX > fromX+1) {
8317 board[fromY][fromX] = EmptySquare;
8318 board[toY][toX] = king;
8319 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8320 board[fromY][BOARD_RGHT-1] = EmptySquare;
8321 } else if (board[fromY][fromX] == king
8322 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8323 && toY == fromY && toX < fromX-1) {
8324 board[fromY][fromX] = EmptySquare;
8325 board[toY][toX] = king;
8326 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8327 board[fromY][BOARD_LEFT] = EmptySquare;
8328 } else if (fromY == 7 && fromX == 3
8329 && board[fromY][fromX] == BlackKing
8330 && toY == 7 && toX == 5) {
8331 board[fromY][fromX] = EmptySquare;
8332 board[toY][toX] = BlackKing;
8333 board[fromY][7] = EmptySquare;
8334 board[toY][4] = BlackRook;
8335 } else if (fromY == 7 && fromX == 3
8336 && board[fromY][fromX] == BlackKing
8337 && toY == 7 && toX == 1) {
8338 board[fromY][fromX] = EmptySquare;
8339 board[toY][toX] = BlackKing;
8340 board[fromY][0] = EmptySquare;
8341 board[toY][2] = BlackRook;
8342 } else if (board[fromY][fromX] == BlackPawn
8344 && gameInfo.variant != VariantXiangqi
8346 /* black pawn promotion */
8347 board[toY][toX] = CharToPiece(ToLower(promoChar));
8348 if (board[toY][toX] == EmptySquare) {
8349 board[toY][toX] = BlackQueen;
8351 if(gameInfo.variant==VariantBughouse ||
8352 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8353 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8354 board[fromY][fromX] = EmptySquare;
8355 } else if ((fromY == 3)
8357 && gameInfo.variant != VariantXiangqi
8358 && gameInfo.variant != VariantBerolina
8359 && (board[fromY][fromX] == BlackPawn)
8360 && (board[toY][toX] == EmptySquare)) {
8361 board[fromY][fromX] = EmptySquare;
8362 board[toY][toX] = BlackPawn;
8363 captured = board[toY + 1][toX];
8364 board[toY + 1][toX] = EmptySquare;
8365 } else if ((fromY == 3)
8367 && gameInfo.variant == VariantBerolina
8368 && (board[fromY][fromX] == BlackPawn)
8369 && (board[toY][toX] == EmptySquare)) {
8370 board[fromY][fromX] = EmptySquare;
8371 board[toY][toX] = BlackPawn;
8372 if(oldEP & EP_BEROLIN_A) {
8373 captured = board[fromY][fromX-1];
8374 board[fromY][fromX-1] = EmptySquare;
8375 }else{ captured = board[fromY][fromX+1];
8376 board[fromY][fromX+1] = EmptySquare;
8379 board[toY][toX] = board[fromY][fromX];
8380 board[fromY][fromX] = EmptySquare;
8383 /* [HGM] now we promote for Shogi, if needed */
8384 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8385 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8388 if (gameInfo.holdingsWidth != 0) {
8390 /* !!A lot more code needs to be written to support holdings */
8391 /* [HGM] OK, so I have written it. Holdings are stored in the */
8392 /* penultimate board files, so they are automaticlly stored */
8393 /* in the game history. */
8394 if (fromY == DROP_RANK) {
8395 /* Delete from holdings, by decreasing count */
8396 /* and erasing image if necessary */
8398 if(p < (int) BlackPawn) { /* white drop */
8399 p -= (int)WhitePawn;
8400 p = PieceToNumber((ChessSquare)p);
8401 if(p >= gameInfo.holdingsSize) p = 0;
8402 if(--board[p][BOARD_WIDTH-2] <= 0)
8403 board[p][BOARD_WIDTH-1] = EmptySquare;
8404 if((int)board[p][BOARD_WIDTH-2] < 0)
8405 board[p][BOARD_WIDTH-2] = 0;
8406 } else { /* black drop */
8407 p -= (int)BlackPawn;
8408 p = PieceToNumber((ChessSquare)p);
8409 if(p >= gameInfo.holdingsSize) p = 0;
8410 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8411 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8412 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8413 board[BOARD_HEIGHT-1-p][1] = 0;
8416 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8417 && gameInfo.variant != VariantBughouse ) {
8418 /* [HGM] holdings: Add to holdings, if holdings exist */
8419 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8420 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8421 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8424 if (p >= (int) BlackPawn) {
8425 p -= (int)BlackPawn;
8426 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8427 /* in Shogi restore piece to its original first */
8428 captured = (ChessSquare) (DEMOTED captured);
8431 p = PieceToNumber((ChessSquare)p);
8432 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8433 board[p][BOARD_WIDTH-2]++;
8434 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8436 p -= (int)WhitePawn;
8437 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8438 captured = (ChessSquare) (DEMOTED captured);
8441 p = PieceToNumber((ChessSquare)p);
8442 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8443 board[BOARD_HEIGHT-1-p][1]++;
8444 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8447 } else if (gameInfo.variant == VariantAtomic) {
8448 if (captured != EmptySquare) {
8450 for (y = toY-1; y <= toY+1; y++) {
8451 for (x = toX-1; x <= toX+1; x++) {
8452 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8453 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8454 board[y][x] = EmptySquare;
8458 board[toY][toX] = EmptySquare;
8461 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8462 /* [HGM] Shogi promotions */
8463 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8466 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8467 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8468 // [HGM] superchess: take promotion piece out of holdings
8469 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8470 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8471 if(!--board[k][BOARD_WIDTH-2])
8472 board[k][BOARD_WIDTH-1] = EmptySquare;
8474 if(!--board[BOARD_HEIGHT-1-k][1])
8475 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8481 /* Updates forwardMostMove */
8483 MakeMove(fromX, fromY, toX, toY, promoChar)
8484 int fromX, fromY, toX, toY;
8487 // forwardMostMove++; // [HGM] bare: moved downstream
8489 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8490 int timeLeft; static int lastLoadFlag=0; int king, piece;
8491 piece = boards[forwardMostMove][fromY][fromX];
8492 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8493 if(gameInfo.variant == VariantKnightmate)
8494 king += (int) WhiteUnicorn - (int) WhiteKing;
8495 if(forwardMostMove == 0) {
8497 fprintf(serverMoves, "%s;", second.tidy);
8498 fprintf(serverMoves, "%s;", first.tidy);
8499 if(!blackPlaysFirst)
8500 fprintf(serverMoves, "%s;", second.tidy);
8501 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8502 lastLoadFlag = loadFlag;
8504 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8505 // print castling suffix
8506 if( toY == fromY && piece == king ) {
8508 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8510 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8513 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8514 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8515 boards[forwardMostMove][toY][toX] == EmptySquare
8517 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8519 if(promoChar != NULLCHAR)
8520 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8522 fprintf(serverMoves, "/%d/%d",
8523 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8524 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8525 else timeLeft = blackTimeRemaining/1000;
8526 fprintf(serverMoves, "/%d", timeLeft);
8528 fflush(serverMoves);
8531 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8532 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8536 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8537 if (commentList[forwardMostMove+1] != NULL) {
8538 free(commentList[forwardMostMove+1]);
8539 commentList[forwardMostMove+1] = NULL;
8541 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8542 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8543 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8544 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8545 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8546 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8547 gameInfo.result = GameUnfinished;
8548 if (gameInfo.resultDetails != NULL) {
8549 free(gameInfo.resultDetails);
8550 gameInfo.resultDetails = NULL;
8552 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8553 moveList[forwardMostMove - 1]);
8554 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8555 PosFlags(forwardMostMove - 1),
8556 fromY, fromX, toY, toX, promoChar,
8557 parseList[forwardMostMove - 1]);
8558 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8564 if(gameInfo.variant != VariantShogi)
8565 strcat(parseList[forwardMostMove - 1], "+");
8569 strcat(parseList[forwardMostMove - 1], "#");
8572 if (appData.debugMode) {
8573 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8578 /* Updates currentMove if not pausing */
8580 ShowMove(fromX, fromY, toX, toY)
8582 int instant = (gameMode == PlayFromGameFile) ?
8583 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8584 if(appData.noGUI) return;
8585 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8587 if (forwardMostMove == currentMove + 1) {
8588 AnimateMove(boards[forwardMostMove - 1],
8589 fromX, fromY, toX, toY);
8591 if (appData.highlightLastMove) {
8592 SetHighlights(fromX, fromY, toX, toY);
8595 currentMove = forwardMostMove;
8598 if (instant) return;
8600 DisplayMove(currentMove - 1);
8601 DrawPosition(FALSE, boards[currentMove]);
8602 DisplayBothClocks();
8603 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8606 void SendEgtPath(ChessProgramState *cps)
8607 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8608 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8610 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8613 char c, *q = name+1, *r, *s;
8615 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8616 while(*p && *p != ',') *q++ = *p++;
8618 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8619 strcmp(name, ",nalimov:") == 0 ) {
8620 // take nalimov path from the menu-changeable option first, if it is defined
8621 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8622 SendToProgram(buf,cps); // send egtbpath command for nalimov
8624 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8625 (s = StrStr(appData.egtFormats, name)) != NULL) {
8626 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8627 s = r = StrStr(s, ":") + 1; // beginning of path info
8628 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8629 c = *r; *r = 0; // temporarily null-terminate path info
8630 *--q = 0; // strip of trailig ':' from name
8631 sprintf(buf, "egtpath %s %s\n", name+1, s);
8633 SendToProgram(buf,cps); // send egtbpath command for this format
8635 if(*p == ',') p++; // read away comma to position for next format name
8640 InitChessProgram(cps, setup)
8641 ChessProgramState *cps;
8642 int setup; /* [HGM] needed to setup FRC opening position */
8644 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8645 if (appData.noChessProgram) return;
8646 hintRequested = FALSE;
8647 bookRequested = FALSE;
8649 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8650 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8651 if(cps->memSize) { /* [HGM] memory */
8652 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8653 SendToProgram(buf, cps);
8655 SendEgtPath(cps); /* [HGM] EGT */
8656 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8657 sprintf(buf, "cores %d\n", appData.smpCores);
8658 SendToProgram(buf, cps);
8661 SendToProgram(cps->initString, cps);
8662 if (gameInfo.variant != VariantNormal &&
8663 gameInfo.variant != VariantLoadable
8664 /* [HGM] also send variant if board size non-standard */
8665 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8667 char *v = VariantName(gameInfo.variant);
8668 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8669 /* [HGM] in protocol 1 we have to assume all variants valid */
8670 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8671 DisplayFatalError(buf, 0, 1);
8675 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8676 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8677 if( gameInfo.variant == VariantXiangqi )
8678 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8679 if( gameInfo.variant == VariantShogi )
8680 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8681 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8682 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8683 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8684 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8685 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8686 if( gameInfo.variant == VariantCourier )
8687 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8688 if( gameInfo.variant == VariantSuper )
8689 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8690 if( gameInfo.variant == VariantGreat )
8691 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8694 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8695 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8696 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8697 if(StrStr(cps->variants, b) == NULL) {
8698 // specific sized variant not known, check if general sizing allowed
8699 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8700 if(StrStr(cps->variants, "boardsize") == NULL) {
8701 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8702 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8703 DisplayFatalError(buf, 0, 1);
8706 /* [HGM] here we really should compare with the maximum supported board size */
8709 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8710 sprintf(buf, "variant %s\n", b);
8711 SendToProgram(buf, cps);
8713 currentlyInitializedVariant = gameInfo.variant;
8715 /* [HGM] send opening position in FRC to first engine */
8717 SendToProgram("force\n", cps);
8719 /* engine is now in force mode! Set flag to wake it up after first move. */
8720 setboardSpoiledMachineBlack = 1;
8724 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8725 SendToProgram(buf, cps);
8727 cps->maybeThinking = FALSE;
8728 cps->offeredDraw = 0;
8729 if (!appData.icsActive) {
8730 SendTimeControl(cps, movesPerSession, timeControl,
8731 timeIncrement, appData.searchDepth,
8734 if (appData.showThinking
8735 // [HGM] thinking: four options require thinking output to be sent
8736 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8738 SendToProgram("post\n", cps);
8740 SendToProgram("hard\n", cps);
8741 if (!appData.ponderNextMove) {
8742 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8743 it without being sure what state we are in first. "hard"
8744 is not a toggle, so that one is OK.
8746 SendToProgram("easy\n", cps);
8749 sprintf(buf, "ping %d\n", ++cps->lastPing);
8750 SendToProgram(buf, cps);
8752 cps->initDone = TRUE;
8757 StartChessProgram(cps)
8758 ChessProgramState *cps;
8763 if (appData.noChessProgram) return;
8764 cps->initDone = FALSE;
8766 if (strcmp(cps->host, "localhost") == 0) {
8767 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8768 } else if (*appData.remoteShell == NULLCHAR) {
8769 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8771 if (*appData.remoteUser == NULLCHAR) {
8772 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8775 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8776 cps->host, appData.remoteUser, cps->program);
8778 err = StartChildProcess(buf, "", &cps->pr);
8782 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8783 DisplayFatalError(buf, err, 1);
8789 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8790 if (cps->protocolVersion > 1) {
8791 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8792 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8793 cps->comboCnt = 0; // and values of combo boxes
8794 SendToProgram(buf, cps);
8796 SendToProgram("xboard\n", cps);
8802 TwoMachinesEventIfReady P((void))
8804 if (first.lastPing != first.lastPong) {
8805 DisplayMessage("", _("Waiting for first chess program"));
8806 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8809 if (second.lastPing != second.lastPong) {
8810 DisplayMessage("", _("Waiting for second chess program"));
8811 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8819 NextMatchGame P((void))
8821 int index; /* [HGM] autoinc: step load index during match */
8823 if (*appData.loadGameFile != NULLCHAR) {
8824 index = appData.loadGameIndex;
8825 if(index < 0) { // [HGM] autoinc
8826 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8827 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8829 LoadGameFromFile(appData.loadGameFile,
8831 appData.loadGameFile, FALSE);
8832 } else if (*appData.loadPositionFile != NULLCHAR) {
8833 index = appData.loadPositionIndex;
8834 if(index < 0) { // [HGM] autoinc
8835 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8836 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8838 LoadPositionFromFile(appData.loadPositionFile,
8840 appData.loadPositionFile);
8842 TwoMachinesEventIfReady();
8845 void UserAdjudicationEvent( int result )
8847 ChessMove gameResult = GameIsDrawn;
8850 gameResult = WhiteWins;
8852 else if( result < 0 ) {
8853 gameResult = BlackWins;
8856 if( gameMode == TwoMachinesPlay ) {
8857 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8862 // [HGM] save: calculate checksum of game to make games easily identifiable
8863 int StringCheckSum(char *s)
8866 if(s==NULL) return 0;
8867 while(*s) i = i*259 + *s++;
8874 for(i=backwardMostMove; i<forwardMostMove; i++) {
8875 sum += pvInfoList[i].depth;
8876 sum += StringCheckSum(parseList[i]);
8877 sum += StringCheckSum(commentList[i]);
8880 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8881 return sum + StringCheckSum(commentList[i]);
8882 } // end of save patch
8885 GameEnds(result, resultDetails, whosays)
8887 char *resultDetails;
8890 GameMode nextGameMode;
8894 if(endingGame) return; /* [HGM] crash: forbid recursion */
8897 if (appData.debugMode) {
8898 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8899 result, resultDetails ? resultDetails : "(null)", whosays);
8902 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8904 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8905 /* If we are playing on ICS, the server decides when the
8906 game is over, but the engine can offer to draw, claim
8910 if (appData.zippyPlay && first.initDone) {
8911 if (result == GameIsDrawn) {
8912 /* In case draw still needs to be claimed */
8913 SendToICS(ics_prefix);
8914 SendToICS("draw\n");
8915 } else if (StrCaseStr(resultDetails, "resign")) {
8916 SendToICS(ics_prefix);
8917 SendToICS("resign\n");
8921 endingGame = 0; /* [HGM] crash */
8925 /* If we're loading the game from a file, stop */
8926 if (whosays == GE_FILE) {
8927 (void) StopLoadGameTimer();
8931 /* Cancel draw offers */
8932 first.offeredDraw = second.offeredDraw = 0;
8934 /* If this is an ICS game, only ICS can really say it's done;
8935 if not, anyone can. */
8936 isIcsGame = (gameMode == IcsPlayingWhite ||
8937 gameMode == IcsPlayingBlack ||
8938 gameMode == IcsObserving ||
8939 gameMode == IcsExamining);
8941 if (!isIcsGame || whosays == GE_ICS) {
8942 /* OK -- not an ICS game, or ICS said it was done */
8944 if (!isIcsGame && !appData.noChessProgram)
8945 SetUserThinkingEnables();
8947 /* [HGM] if a machine claims the game end we verify this claim */
8948 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8949 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8951 ChessMove trueResult = (ChessMove) -1;
8953 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8954 first.twoMachinesColor[0] :
8955 second.twoMachinesColor[0] ;
8957 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8958 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8959 /* [HGM] verify: engine mate claims accepted if they were flagged */
8960 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8962 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8963 /* [HGM] verify: engine mate claims accepted if they were flagged */
8964 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8966 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8967 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8970 // now verify win claims, but not in drop games, as we don't understand those yet
8971 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8972 || gameInfo.variant == VariantGreat) &&
8973 (result == WhiteWins && claimer == 'w' ||
8974 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8975 if (appData.debugMode) {
8976 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8977 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8979 if(result != trueResult) {
8980 sprintf(buf, "False win claim: '%s'", resultDetails);
8981 result = claimer == 'w' ? BlackWins : WhiteWins;
8982 resultDetails = buf;
8985 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8986 && (forwardMostMove <= backwardMostMove ||
8987 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8988 (claimer=='b')==(forwardMostMove&1))
8990 /* [HGM] verify: draws that were not flagged are false claims */
8991 sprintf(buf, "False draw claim: '%s'", resultDetails);
8992 result = claimer == 'w' ? BlackWins : WhiteWins;
8993 resultDetails = buf;
8995 /* (Claiming a loss is accepted no questions asked!) */
8997 /* [HGM] bare: don't allow bare King to win */
8998 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8999 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9000 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9001 && result != GameIsDrawn)
9002 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9003 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9004 int p = (signed char)boards[forwardMostMove][i][j] - color;
9005 if(p >= 0 && p <= (int)WhiteKing) k++;
9007 if (appData.debugMode) {
9008 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9009 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9012 result = GameIsDrawn;
9013 sprintf(buf, "%s but bare king", resultDetails);
9014 resultDetails = buf;
9020 if(serverMoves != NULL && !loadFlag) { char c = '=';
9021 if(result==WhiteWins) c = '+';
9022 if(result==BlackWins) c = '-';
9023 if(resultDetails != NULL)
9024 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9026 if (resultDetails != NULL) {
9027 gameInfo.result = result;
9028 gameInfo.resultDetails = StrSave(resultDetails);
9030 /* display last move only if game was not loaded from file */
9031 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9032 DisplayMove(currentMove - 1);
9034 if (forwardMostMove != 0) {
9035 if (gameMode != PlayFromGameFile && gameMode != EditGame
9036 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9038 if (*appData.saveGameFile != NULLCHAR) {
9039 SaveGameToFile(appData.saveGameFile, TRUE);
9040 } else if (appData.autoSaveGames) {
9043 if (*appData.savePositionFile != NULLCHAR) {
9044 SavePositionToFile(appData.savePositionFile);
9049 /* Tell program how game ended in case it is learning */
9050 /* [HGM] Moved this to after saving the PGN, just in case */
9051 /* engine died and we got here through time loss. In that */
9052 /* case we will get a fatal error writing the pipe, which */
9053 /* would otherwise lose us the PGN. */
9054 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9055 /* output during GameEnds should never be fatal anymore */
9056 if (gameMode == MachinePlaysWhite ||
9057 gameMode == MachinePlaysBlack ||
9058 gameMode == TwoMachinesPlay ||
9059 gameMode == IcsPlayingWhite ||
9060 gameMode == IcsPlayingBlack ||
9061 gameMode == BeginningOfGame) {
9063 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9065 if (first.pr != NoProc) {
9066 SendToProgram(buf, &first);
9068 if (second.pr != NoProc &&
9069 gameMode == TwoMachinesPlay) {
9070 SendToProgram(buf, &second);
9075 if (appData.icsActive) {
9076 if (appData.quietPlay &&
9077 (gameMode == IcsPlayingWhite ||
9078 gameMode == IcsPlayingBlack)) {
9079 SendToICS(ics_prefix);
9080 SendToICS("set shout 1\n");
9082 nextGameMode = IcsIdle;
9083 ics_user_moved = FALSE;
9084 /* clean up premove. It's ugly when the game has ended and the
9085 * premove highlights are still on the board.
9089 ClearPremoveHighlights();
9090 DrawPosition(FALSE, boards[currentMove]);
9092 if (whosays == GE_ICS) {
9095 if (gameMode == IcsPlayingWhite)
9097 else if(gameMode == IcsPlayingBlack)
9101 if (gameMode == IcsPlayingBlack)
9103 else if(gameMode == IcsPlayingWhite)
9110 PlayIcsUnfinishedSound();
9113 } else if (gameMode == EditGame ||
9114 gameMode == PlayFromGameFile ||
9115 gameMode == AnalyzeMode ||
9116 gameMode == AnalyzeFile) {
9117 nextGameMode = gameMode;
9119 nextGameMode = EndOfGame;
9124 nextGameMode = gameMode;
9127 if (appData.noChessProgram) {
9128 gameMode = nextGameMode;
9130 endingGame = 0; /* [HGM] crash */
9135 /* Put first chess program into idle state */
9136 if (first.pr != NoProc &&
9137 (gameMode == MachinePlaysWhite ||
9138 gameMode == MachinePlaysBlack ||
9139 gameMode == TwoMachinesPlay ||
9140 gameMode == IcsPlayingWhite ||
9141 gameMode == IcsPlayingBlack ||
9142 gameMode == BeginningOfGame)) {
9143 SendToProgram("force\n", &first);
9144 if (first.usePing) {
9146 sprintf(buf, "ping %d\n", ++first.lastPing);
9147 SendToProgram(buf, &first);
9150 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9151 /* Kill off first chess program */
9152 if (first.isr != NULL)
9153 RemoveInputSource(first.isr);
9156 if (first.pr != NoProc) {
9158 DoSleep( appData.delayBeforeQuit );
9159 SendToProgram("quit\n", &first);
9160 DoSleep( appData.delayAfterQuit );
9161 DestroyChildProcess(first.pr, first.useSigterm);
9166 /* Put second chess program into idle state */
9167 if (second.pr != NoProc &&
9168 gameMode == TwoMachinesPlay) {
9169 SendToProgram("force\n", &second);
9170 if (second.usePing) {
9172 sprintf(buf, "ping %d\n", ++second.lastPing);
9173 SendToProgram(buf, &second);
9176 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9177 /* Kill off second chess program */
9178 if (second.isr != NULL)
9179 RemoveInputSource(second.isr);
9182 if (second.pr != NoProc) {
9183 DoSleep( appData.delayBeforeQuit );
9184 SendToProgram("quit\n", &second);
9185 DoSleep( appData.delayAfterQuit );
9186 DestroyChildProcess(second.pr, second.useSigterm);
9191 if (matchMode && gameMode == TwoMachinesPlay) {
9194 if (first.twoMachinesColor[0] == 'w') {
9201 if (first.twoMachinesColor[0] == 'b') {
9210 if (matchGame < appData.matchGames) {
9212 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9213 tmp = first.twoMachinesColor;
9214 first.twoMachinesColor = second.twoMachinesColor;
9215 second.twoMachinesColor = tmp;
9217 gameMode = nextGameMode;
9219 if(appData.matchPause>10000 || appData.matchPause<10)
9220 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9221 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9222 endingGame = 0; /* [HGM] crash */
9226 gameMode = nextGameMode;
9227 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9228 first.tidy, second.tidy,
9229 first.matchWins, second.matchWins,
9230 appData.matchGames - (first.matchWins + second.matchWins));
9231 DisplayFatalError(buf, 0, 0);
9234 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9235 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9237 gameMode = nextGameMode;
9239 endingGame = 0; /* [HGM] crash */
9242 /* Assumes program was just initialized (initString sent).
9243 Leaves program in force mode. */
9245 FeedMovesToProgram(cps, upto)
9246 ChessProgramState *cps;
9251 if (appData.debugMode)
9252 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9253 startedFromSetupPosition ? "position and " : "",
9254 backwardMostMove, upto, cps->which);
9255 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9256 // [HGM] variantswitch: make engine aware of new variant
9257 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9258 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9259 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9260 SendToProgram(buf, cps);
9261 currentlyInitializedVariant = gameInfo.variant;
9263 SendToProgram("force\n", cps);
9264 if (startedFromSetupPosition) {
9265 SendBoard(cps, backwardMostMove);
9266 if (appData.debugMode) {
9267 fprintf(debugFP, "feedMoves\n");
9270 for (i = backwardMostMove; i < upto; i++) {
9271 SendMoveToProgram(i, cps);
9277 ResurrectChessProgram()
9279 /* The chess program may have exited.
9280 If so, restart it and feed it all the moves made so far. */
9282 if (appData.noChessProgram || first.pr != NoProc) return;
9284 StartChessProgram(&first);
9285 InitChessProgram(&first, FALSE);
9286 FeedMovesToProgram(&first, currentMove);
9288 if (!first.sendTime) {
9289 /* can't tell gnuchess what its clock should read,
9290 so we bow to its notion. */
9292 timeRemaining[0][currentMove] = whiteTimeRemaining;
9293 timeRemaining[1][currentMove] = blackTimeRemaining;
9296 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9297 appData.icsEngineAnalyze) && first.analysisSupport) {
9298 SendToProgram("analyze\n", &first);
9299 first.analyzing = TRUE;
9312 if (appData.debugMode) {
9313 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9314 redraw, init, gameMode);
9316 CleanupTail(); // [HGM] vari: delete any stored variations
9317 pausing = pauseExamInvalid = FALSE;
9318 startedFromSetupPosition = blackPlaysFirst = FALSE;
9320 whiteFlag = blackFlag = FALSE;
9321 userOfferedDraw = FALSE;
9322 hintRequested = bookRequested = FALSE;
9323 first.maybeThinking = FALSE;
9324 second.maybeThinking = FALSE;
9325 first.bookSuspend = FALSE; // [HGM] book
9326 second.bookSuspend = FALSE;
9327 thinkOutput[0] = NULLCHAR;
9328 lastHint[0] = NULLCHAR;
9329 ClearGameInfo(&gameInfo);
9330 gameInfo.variant = StringToVariant(appData.variant);
9331 ics_user_moved = ics_clock_paused = FALSE;
9332 ics_getting_history = H_FALSE;
9334 white_holding[0] = black_holding[0] = NULLCHAR;
9335 ClearProgramStats();
9336 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9340 flipView = appData.flipView;
9341 ClearPremoveHighlights();
9343 alarmSounded = FALSE;
9345 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9346 if(appData.serverMovesName != NULL) {
9347 /* [HGM] prepare to make moves file for broadcasting */
9348 clock_t t = clock();
9349 if(serverMoves != NULL) fclose(serverMoves);
9350 serverMoves = fopen(appData.serverMovesName, "r");
9351 if(serverMoves != NULL) {
9352 fclose(serverMoves);
9353 /* delay 15 sec before overwriting, so all clients can see end */
9354 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9356 serverMoves = fopen(appData.serverMovesName, "w");
9360 gameMode = BeginningOfGame;
9362 if(appData.icsActive) gameInfo.variant = VariantNormal;
9363 currentMove = forwardMostMove = backwardMostMove = 0;
9364 InitPosition(redraw);
9365 for (i = 0; i < MAX_MOVES; i++) {
9366 if (commentList[i] != NULL) {
9367 free(commentList[i]);
9368 commentList[i] = NULL;
9372 timeRemaining[0][0] = whiteTimeRemaining;
9373 timeRemaining[1][0] = blackTimeRemaining;
9374 if (first.pr == NULL) {
9375 StartChessProgram(&first);
9378 InitChessProgram(&first, startedFromSetupPosition);
9381 DisplayMessage("", "");
9382 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9383 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9390 if (!AutoPlayOneMove())
9392 if (matchMode || appData.timeDelay == 0)
9394 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9396 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9405 int fromX, fromY, toX, toY;
9407 if (appData.debugMode) {
9408 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9411 if (gameMode != PlayFromGameFile)
9414 if (currentMove >= forwardMostMove) {
9415 gameMode = EditGame;
9418 /* [AS] Clear current move marker at the end of a game */
9419 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9424 toX = moveList[currentMove][2] - AAA;
9425 toY = moveList[currentMove][3] - ONE;
9427 if (moveList[currentMove][1] == '@') {
9428 if (appData.highlightLastMove) {
9429 SetHighlights(-1, -1, toX, toY);
9432 fromX = moveList[currentMove][0] - AAA;
9433 fromY = moveList[currentMove][1] - ONE;
9435 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9437 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9439 if (appData.highlightLastMove) {
9440 SetHighlights(fromX, fromY, toX, toY);
9443 DisplayMove(currentMove);
9444 SendMoveToProgram(currentMove++, &first);
9445 DisplayBothClocks();
9446 DrawPosition(FALSE, boards[currentMove]);
9447 // [HGM] PV info: always display, routine tests if empty
9448 DisplayComment(currentMove - 1, commentList[currentMove]);
9454 LoadGameOneMove(readAhead)
9455 ChessMove readAhead;
9457 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9458 char promoChar = NULLCHAR;
9463 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9464 gameMode != AnalyzeMode && gameMode != Training) {
9469 yyboardindex = forwardMostMove;
9470 if (readAhead != (ChessMove)0) {
9471 moveType = readAhead;
9473 if (gameFileFP == NULL)
9475 moveType = (ChessMove) yylex();
9481 if (appData.debugMode)
9482 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9485 /* append the comment but don't display it */
9486 AppendComment(currentMove, p, FALSE);
9489 case WhiteCapturesEnPassant:
9490 case BlackCapturesEnPassant:
9491 case WhitePromotionChancellor:
9492 case BlackPromotionChancellor:
9493 case WhitePromotionArchbishop:
9494 case BlackPromotionArchbishop:
9495 case WhitePromotionCentaur:
9496 case BlackPromotionCentaur:
9497 case WhitePromotionQueen:
9498 case BlackPromotionQueen:
9499 case WhitePromotionRook:
9500 case BlackPromotionRook:
9501 case WhitePromotionBishop:
9502 case BlackPromotionBishop:
9503 case WhitePromotionKnight:
9504 case BlackPromotionKnight:
9505 case WhitePromotionKing:
9506 case BlackPromotionKing:
9508 case WhiteKingSideCastle:
9509 case WhiteQueenSideCastle:
9510 case BlackKingSideCastle:
9511 case BlackQueenSideCastle:
9512 case WhiteKingSideCastleWild:
9513 case WhiteQueenSideCastleWild:
9514 case BlackKingSideCastleWild:
9515 case BlackQueenSideCastleWild:
9517 case WhiteHSideCastleFR:
9518 case WhiteASideCastleFR:
9519 case BlackHSideCastleFR:
9520 case BlackASideCastleFR:
9522 if (appData.debugMode)
9523 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9524 fromX = currentMoveString[0] - AAA;
9525 fromY = currentMoveString[1] - ONE;
9526 toX = currentMoveString[2] - AAA;
9527 toY = currentMoveString[3] - ONE;
9528 promoChar = currentMoveString[4];
9533 if (appData.debugMode)
9534 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9535 fromX = moveType == WhiteDrop ?
9536 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9537 (int) CharToPiece(ToLower(currentMoveString[0]));
9539 toX = currentMoveString[2] - AAA;
9540 toY = currentMoveString[3] - ONE;
9546 case GameUnfinished:
9547 if (appData.debugMode)
9548 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9549 p = strchr(yy_text, '{');
9550 if (p == NULL) p = strchr(yy_text, '(');
9553 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9555 q = strchr(p, *p == '{' ? '}' : ')');
9556 if (q != NULL) *q = NULLCHAR;
9559 GameEnds(moveType, p, GE_FILE);
9561 if (cmailMsgLoaded) {
9563 flipView = WhiteOnMove(currentMove);
9564 if (moveType == GameUnfinished) flipView = !flipView;
9565 if (appData.debugMode)
9566 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9570 case (ChessMove) 0: /* end of file */
9571 if (appData.debugMode)
9572 fprintf(debugFP, "Parser hit end of file\n");
9573 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9579 if (WhiteOnMove(currentMove)) {
9580 GameEnds(BlackWins, "Black mates", GE_FILE);
9582 GameEnds(WhiteWins, "White mates", GE_FILE);
9586 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9593 if (lastLoadGameStart == GNUChessGame) {
9594 /* GNUChessGames have numbers, but they aren't move numbers */
9595 if (appData.debugMode)
9596 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9597 yy_text, (int) moveType);
9598 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9600 /* else fall thru */
9605 /* Reached start of next game in file */
9606 if (appData.debugMode)
9607 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9608 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9614 if (WhiteOnMove(currentMove)) {
9615 GameEnds(BlackWins, "Black mates", GE_FILE);
9617 GameEnds(WhiteWins, "White mates", GE_FILE);
9621 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9627 case PositionDiagram: /* should not happen; ignore */
9628 case ElapsedTime: /* ignore */
9629 case NAG: /* ignore */
9630 if (appData.debugMode)
9631 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9632 yy_text, (int) moveType);
9633 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9636 if (appData.testLegality) {
9637 if (appData.debugMode)
9638 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9639 sprintf(move, _("Illegal move: %d.%s%s"),
9640 (forwardMostMove / 2) + 1,
9641 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9642 DisplayError(move, 0);
9645 if (appData.debugMode)
9646 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9647 yy_text, currentMoveString);
9648 fromX = currentMoveString[0] - AAA;
9649 fromY = currentMoveString[1] - ONE;
9650 toX = currentMoveString[2] - AAA;
9651 toY = currentMoveString[3] - ONE;
9652 promoChar = currentMoveString[4];
9657 if (appData.debugMode)
9658 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9659 sprintf(move, _("Ambiguous move: %d.%s%s"),
9660 (forwardMostMove / 2) + 1,
9661 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9662 DisplayError(move, 0);
9667 case ImpossibleMove:
9668 if (appData.debugMode)
9669 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9670 sprintf(move, _("Illegal move: %d.%s%s"),
9671 (forwardMostMove / 2) + 1,
9672 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9673 DisplayError(move, 0);
9679 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9680 DrawPosition(FALSE, boards[currentMove]);
9681 DisplayBothClocks();
9682 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9683 DisplayComment(currentMove - 1, commentList[currentMove]);
9685 (void) StopLoadGameTimer();
9687 cmailOldMove = forwardMostMove;
9690 /* currentMoveString is set as a side-effect of yylex */
9691 strcat(currentMoveString, "\n");
9692 strcpy(moveList[forwardMostMove], currentMoveString);
9694 thinkOutput[0] = NULLCHAR;
9695 MakeMove(fromX, fromY, toX, toY, promoChar);
9696 currentMove = forwardMostMove;
9701 /* Load the nth game from the given file */
9703 LoadGameFromFile(filename, n, title, useList)
9707 /*Boolean*/ int useList;
9712 if (strcmp(filename, "-") == 0) {
9716 f = fopen(filename, "rb");
9718 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9719 DisplayError(buf, errno);
9723 if (fseek(f, 0, 0) == -1) {
9724 /* f is not seekable; probably a pipe */
9727 if (useList && n == 0) {
9728 int error = GameListBuild(f);
9730 DisplayError(_("Cannot build game list"), error);
9731 } else if (!ListEmpty(&gameList) &&
9732 ((ListGame *) gameList.tailPred)->number > 1) {
9733 GameListPopUp(f, title);
9740 return LoadGame(f, n, title, FALSE);
9745 MakeRegisteredMove()
9747 int fromX, fromY, toX, toY;
9749 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9750 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9753 if (appData.debugMode)
9754 fprintf(debugFP, "Restoring %s for game %d\n",
9755 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9757 thinkOutput[0] = NULLCHAR;
9758 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9759 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9760 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9761 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9762 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9763 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9764 MakeMove(fromX, fromY, toX, toY, promoChar);
9765 ShowMove(fromX, fromY, toX, toY);
9767 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9774 if (WhiteOnMove(currentMove)) {
9775 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9777 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9782 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9789 if (WhiteOnMove(currentMove)) {
9790 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9792 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9797 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9808 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9810 CmailLoadGame(f, gameNumber, title, useList)
9818 if (gameNumber > nCmailGames) {
9819 DisplayError(_("No more games in this message"), 0);
9822 if (f == lastLoadGameFP) {
9823 int offset = gameNumber - lastLoadGameNumber;
9825 cmailMsg[0] = NULLCHAR;
9826 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9827 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9828 nCmailMovesRegistered--;
9830 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9831 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9832 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9835 if (! RegisterMove()) return FALSE;
9839 retVal = LoadGame(f, gameNumber, title, useList);
9841 /* Make move registered during previous look at this game, if any */
9842 MakeRegisteredMove();
9844 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9845 commentList[currentMove]
9846 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9847 DisplayComment(currentMove - 1, commentList[currentMove]);
9853 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9858 int gameNumber = lastLoadGameNumber + offset;
9859 if (lastLoadGameFP == NULL) {
9860 DisplayError(_("No game has been loaded yet"), 0);
9863 if (gameNumber <= 0) {
9864 DisplayError(_("Can't back up any further"), 0);
9867 if (cmailMsgLoaded) {
9868 return CmailLoadGame(lastLoadGameFP, gameNumber,
9869 lastLoadGameTitle, lastLoadGameUseList);
9871 return LoadGame(lastLoadGameFP, gameNumber,
9872 lastLoadGameTitle, lastLoadGameUseList);
9878 /* Load the nth game from open file f */
9880 LoadGame(f, gameNumber, title, useList)
9888 int gn = gameNumber;
9889 ListGame *lg = NULL;
9892 GameMode oldGameMode;
9893 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9895 if (appData.debugMode)
9896 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9898 if (gameMode == Training )
9899 SetTrainingModeOff();
9901 oldGameMode = gameMode;
9902 if (gameMode != BeginningOfGame) {
9907 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9908 fclose(lastLoadGameFP);
9912 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9915 fseek(f, lg->offset, 0);
9916 GameListHighlight(gameNumber);
9920 DisplayError(_("Game number out of range"), 0);
9925 if (fseek(f, 0, 0) == -1) {
9926 if (f == lastLoadGameFP ?
9927 gameNumber == lastLoadGameNumber + 1 :
9931 DisplayError(_("Can't seek on game file"), 0);
9937 lastLoadGameNumber = gameNumber;
9938 strcpy(lastLoadGameTitle, title);
9939 lastLoadGameUseList = useList;
9943 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9944 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9945 lg->gameInfo.black);
9947 } else if (*title != NULLCHAR) {
9948 if (gameNumber > 1) {
9949 sprintf(buf, "%s %d", title, gameNumber);
9952 DisplayTitle(title);
9956 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9957 gameMode = PlayFromGameFile;
9961 currentMove = forwardMostMove = backwardMostMove = 0;
9962 CopyBoard(boards[0], initialPosition);
9966 * Skip the first gn-1 games in the file.
9967 * Also skip over anything that precedes an identifiable
9968 * start of game marker, to avoid being confused by
9969 * garbage at the start of the file. Currently
9970 * recognized start of game markers are the move number "1",
9971 * the pattern "gnuchess .* game", the pattern
9972 * "^[#;%] [^ ]* game file", and a PGN tag block.
9973 * A game that starts with one of the latter two patterns
9974 * will also have a move number 1, possibly
9975 * following a position diagram.
9976 * 5-4-02: Let's try being more lenient and allowing a game to
9977 * start with an unnumbered move. Does that break anything?
9979 cm = lastLoadGameStart = (ChessMove) 0;
9981 yyboardindex = forwardMostMove;
9982 cm = (ChessMove) yylex();
9985 if (cmailMsgLoaded) {
9986 nCmailGames = CMAIL_MAX_GAMES - gn;
9989 DisplayError(_("Game not found in file"), 0);
9996 lastLoadGameStart = cm;
10000 switch (lastLoadGameStart) {
10005 case MoveNumberOne:
10006 case (ChessMove) 0:
10007 gn--; /* count this game */
10008 lastLoadGameStart = cm;
10017 switch (lastLoadGameStart) {
10020 case MoveNumberOne:
10021 case (ChessMove) 0:
10022 gn--; /* count this game */
10023 lastLoadGameStart = cm;
10026 lastLoadGameStart = cm; /* game counted already */
10034 yyboardindex = forwardMostMove;
10035 cm = (ChessMove) yylex();
10036 } while (cm == PGNTag || cm == Comment);
10043 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10044 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10045 != CMAIL_OLD_RESULT) {
10047 cmailResult[ CMAIL_MAX_GAMES
10048 - gn - 1] = CMAIL_OLD_RESULT;
10054 /* Only a NormalMove can be at the start of a game
10055 * without a position diagram. */
10056 if (lastLoadGameStart == (ChessMove) 0) {
10058 lastLoadGameStart = MoveNumberOne;
10067 if (appData.debugMode)
10068 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10070 if (cm == XBoardGame) {
10071 /* Skip any header junk before position diagram and/or move 1 */
10073 yyboardindex = forwardMostMove;
10074 cm = (ChessMove) yylex();
10076 if (cm == (ChessMove) 0 ||
10077 cm == GNUChessGame || cm == XBoardGame) {
10078 /* Empty game; pretend end-of-file and handle later */
10079 cm = (ChessMove) 0;
10083 if (cm == MoveNumberOne || cm == PositionDiagram ||
10084 cm == PGNTag || cm == Comment)
10087 } else if (cm == GNUChessGame) {
10088 if (gameInfo.event != NULL) {
10089 free(gameInfo.event);
10091 gameInfo.event = StrSave(yy_text);
10094 startedFromSetupPosition = FALSE;
10095 while (cm == PGNTag) {
10096 if (appData.debugMode)
10097 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10098 err = ParsePGNTag(yy_text, &gameInfo);
10099 if (!err) numPGNTags++;
10101 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10102 if(gameInfo.variant != oldVariant) {
10103 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10104 InitPosition(TRUE);
10105 oldVariant = gameInfo.variant;
10106 if (appData.debugMode)
10107 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10111 if (gameInfo.fen != NULL) {
10112 Board initial_position;
10113 startedFromSetupPosition = TRUE;
10114 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10116 DisplayError(_("Bad FEN position in file"), 0);
10119 CopyBoard(boards[0], initial_position);
10120 if (blackPlaysFirst) {
10121 currentMove = forwardMostMove = backwardMostMove = 1;
10122 CopyBoard(boards[1], initial_position);
10123 strcpy(moveList[0], "");
10124 strcpy(parseList[0], "");
10125 timeRemaining[0][1] = whiteTimeRemaining;
10126 timeRemaining[1][1] = blackTimeRemaining;
10127 if (commentList[0] != NULL) {
10128 commentList[1] = commentList[0];
10129 commentList[0] = NULL;
10132 currentMove = forwardMostMove = backwardMostMove = 0;
10134 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10136 initialRulePlies = FENrulePlies;
10137 for( i=0; i< nrCastlingRights; i++ )
10138 initialRights[i] = initial_position[CASTLING][i];
10140 yyboardindex = forwardMostMove;
10141 free(gameInfo.fen);
10142 gameInfo.fen = NULL;
10145 yyboardindex = forwardMostMove;
10146 cm = (ChessMove) yylex();
10148 /* Handle comments interspersed among the tags */
10149 while (cm == Comment) {
10151 if (appData.debugMode)
10152 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10154 AppendComment(currentMove, p, FALSE);
10155 yyboardindex = forwardMostMove;
10156 cm = (ChessMove) yylex();
10160 /* don't rely on existence of Event tag since if game was
10161 * pasted from clipboard the Event tag may not exist
10163 if (numPGNTags > 0){
10165 if (gameInfo.variant == VariantNormal) {
10166 gameInfo.variant = StringToVariant(gameInfo.event);
10169 if( appData.autoDisplayTags ) {
10170 tags = PGNTags(&gameInfo);
10171 TagsPopUp(tags, CmailMsg());
10176 /* Make something up, but don't display it now */
10181 if (cm == PositionDiagram) {
10184 Board initial_position;
10186 if (appData.debugMode)
10187 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10189 if (!startedFromSetupPosition) {
10191 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10192 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10202 initial_position[i][j++] = CharToPiece(*p);
10205 while (*p == ' ' || *p == '\t' ||
10206 *p == '\n' || *p == '\r') p++;
10208 if (strncmp(p, "black", strlen("black"))==0)
10209 blackPlaysFirst = TRUE;
10211 blackPlaysFirst = FALSE;
10212 startedFromSetupPosition = TRUE;
10214 CopyBoard(boards[0], initial_position);
10215 if (blackPlaysFirst) {
10216 currentMove = forwardMostMove = backwardMostMove = 1;
10217 CopyBoard(boards[1], initial_position);
10218 strcpy(moveList[0], "");
10219 strcpy(parseList[0], "");
10220 timeRemaining[0][1] = whiteTimeRemaining;
10221 timeRemaining[1][1] = blackTimeRemaining;
10222 if (commentList[0] != NULL) {
10223 commentList[1] = commentList[0];
10224 commentList[0] = NULL;
10227 currentMove = forwardMostMove = backwardMostMove = 0;
10230 yyboardindex = forwardMostMove;
10231 cm = (ChessMove) yylex();
10234 if (first.pr == NoProc) {
10235 StartChessProgram(&first);
10237 InitChessProgram(&first, FALSE);
10238 SendToProgram("force\n", &first);
10239 if (startedFromSetupPosition) {
10240 SendBoard(&first, forwardMostMove);
10241 if (appData.debugMode) {
10242 fprintf(debugFP, "Load Game\n");
10244 DisplayBothClocks();
10247 /* [HGM] server: flag to write setup moves in broadcast file as one */
10248 loadFlag = appData.suppressLoadMoves;
10250 while (cm == Comment) {
10252 if (appData.debugMode)
10253 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10255 AppendComment(currentMove, p, FALSE);
10256 yyboardindex = forwardMostMove;
10257 cm = (ChessMove) yylex();
10260 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10261 cm == WhiteWins || cm == BlackWins ||
10262 cm == GameIsDrawn || cm == GameUnfinished) {
10263 DisplayMessage("", _("No moves in game"));
10264 if (cmailMsgLoaded) {
10265 if (appData.debugMode)
10266 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10270 DrawPosition(FALSE, boards[currentMove]);
10271 DisplayBothClocks();
10272 gameMode = EditGame;
10279 // [HGM] PV info: routine tests if comment empty
10280 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10281 DisplayComment(currentMove - 1, commentList[currentMove]);
10283 if (!matchMode && appData.timeDelay != 0)
10284 DrawPosition(FALSE, boards[currentMove]);
10286 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10287 programStats.ok_to_send = 1;
10290 /* if the first token after the PGN tags is a move
10291 * and not move number 1, retrieve it from the parser
10293 if (cm != MoveNumberOne)
10294 LoadGameOneMove(cm);
10296 /* load the remaining moves from the file */
10297 while (LoadGameOneMove((ChessMove)0)) {
10298 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10299 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10302 /* rewind to the start of the game */
10303 currentMove = backwardMostMove;
10305 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10307 if (oldGameMode == AnalyzeFile ||
10308 oldGameMode == AnalyzeMode) {
10309 AnalyzeFileEvent();
10312 if (matchMode || appData.timeDelay == 0) {
10314 gameMode = EditGame;
10316 } else if (appData.timeDelay > 0) {
10317 AutoPlayGameLoop();
10320 if (appData.debugMode)
10321 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10323 loadFlag = 0; /* [HGM] true game starts */
10327 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10329 ReloadPosition(offset)
10332 int positionNumber = lastLoadPositionNumber + offset;
10333 if (lastLoadPositionFP == NULL) {
10334 DisplayError(_("No position has been loaded yet"), 0);
10337 if (positionNumber <= 0) {
10338 DisplayError(_("Can't back up any further"), 0);
10341 return LoadPosition(lastLoadPositionFP, positionNumber,
10342 lastLoadPositionTitle);
10345 /* Load the nth position from the given file */
10347 LoadPositionFromFile(filename, n, title)
10355 if (strcmp(filename, "-") == 0) {
10356 return LoadPosition(stdin, n, "stdin");
10358 f = fopen(filename, "rb");
10360 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10361 DisplayError(buf, errno);
10364 return LoadPosition(f, n, title);
10369 /* Load the nth position from the given open file, and close it */
10371 LoadPosition(f, positionNumber, title)
10373 int positionNumber;
10376 char *p, line[MSG_SIZ];
10377 Board initial_position;
10378 int i, j, fenMode, pn;
10380 if (gameMode == Training )
10381 SetTrainingModeOff();
10383 if (gameMode != BeginningOfGame) {
10384 Reset(FALSE, TRUE);
10386 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10387 fclose(lastLoadPositionFP);
10389 if (positionNumber == 0) positionNumber = 1;
10390 lastLoadPositionFP = f;
10391 lastLoadPositionNumber = positionNumber;
10392 strcpy(lastLoadPositionTitle, title);
10393 if (first.pr == NoProc) {
10394 StartChessProgram(&first);
10395 InitChessProgram(&first, FALSE);
10397 pn = positionNumber;
10398 if (positionNumber < 0) {
10399 /* Negative position number means to seek to that byte offset */
10400 if (fseek(f, -positionNumber, 0) == -1) {
10401 DisplayError(_("Can't seek on position file"), 0);
10406 if (fseek(f, 0, 0) == -1) {
10407 if (f == lastLoadPositionFP ?
10408 positionNumber == lastLoadPositionNumber + 1 :
10409 positionNumber == 1) {
10412 DisplayError(_("Can't seek on position file"), 0);
10417 /* See if this file is FEN or old-style xboard */
10418 if (fgets(line, MSG_SIZ, f) == NULL) {
10419 DisplayError(_("Position not found in file"), 0);
10422 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10423 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10426 if (fenMode || line[0] == '#') pn--;
10428 /* skip positions before number pn */
10429 if (fgets(line, MSG_SIZ, f) == NULL) {
10431 DisplayError(_("Position not found in file"), 0);
10434 if (fenMode || line[0] == '#') pn--;
10439 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10440 DisplayError(_("Bad FEN position in file"), 0);
10444 (void) fgets(line, MSG_SIZ, f);
10445 (void) fgets(line, MSG_SIZ, f);
10447 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10448 (void) fgets(line, MSG_SIZ, f);
10449 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10452 initial_position[i][j++] = CharToPiece(*p);
10456 blackPlaysFirst = FALSE;
10458 (void) fgets(line, MSG_SIZ, f);
10459 if (strncmp(line, "black", strlen("black"))==0)
10460 blackPlaysFirst = TRUE;
10463 startedFromSetupPosition = TRUE;
10465 SendToProgram("force\n", &first);
10466 CopyBoard(boards[0], initial_position);
10467 if (blackPlaysFirst) {
10468 currentMove = forwardMostMove = backwardMostMove = 1;
10469 strcpy(moveList[0], "");
10470 strcpy(parseList[0], "");
10471 CopyBoard(boards[1], initial_position);
10472 DisplayMessage("", _("Black to play"));
10474 currentMove = forwardMostMove = backwardMostMove = 0;
10475 DisplayMessage("", _("White to play"));
10477 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10478 SendBoard(&first, forwardMostMove);
10479 if (appData.debugMode) {
10481 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10482 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10483 fprintf(debugFP, "Load Position\n");
10486 if (positionNumber > 1) {
10487 sprintf(line, "%s %d", title, positionNumber);
10488 DisplayTitle(line);
10490 DisplayTitle(title);
10492 gameMode = EditGame;
10495 timeRemaining[0][1] = whiteTimeRemaining;
10496 timeRemaining[1][1] = blackTimeRemaining;
10497 DrawPosition(FALSE, boards[currentMove]);
10504 CopyPlayerNameIntoFileName(dest, src)
10507 while (*src != NULLCHAR && *src != ',') {
10512 *(*dest)++ = *src++;
10517 char *DefaultFileName(ext)
10520 static char def[MSG_SIZ];
10523 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10525 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10527 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10536 /* Save the current game to the given file */
10538 SaveGameToFile(filename, append)
10545 if (strcmp(filename, "-") == 0) {
10546 return SaveGame(stdout, 0, NULL);
10548 f = fopen(filename, append ? "a" : "w");
10550 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10551 DisplayError(buf, errno);
10554 return SaveGame(f, 0, NULL);
10563 static char buf[MSG_SIZ];
10566 p = strchr(str, ' ');
10567 if (p == NULL) return str;
10568 strncpy(buf, str, p - str);
10569 buf[p - str] = NULLCHAR;
10573 #define PGN_MAX_LINE 75
10575 #define PGN_SIDE_WHITE 0
10576 #define PGN_SIDE_BLACK 1
10579 static int FindFirstMoveOutOfBook( int side )
10583 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10584 int index = backwardMostMove;
10585 int has_book_hit = 0;
10587 if( (index % 2) != side ) {
10591 while( index < forwardMostMove ) {
10592 /* Check to see if engine is in book */
10593 int depth = pvInfoList[index].depth;
10594 int score = pvInfoList[index].score;
10600 else if( score == 0 && depth == 63 ) {
10601 in_book = 1; /* Zappa */
10603 else if( score == 2 && depth == 99 ) {
10604 in_book = 1; /* Abrok */
10607 has_book_hit += in_book;
10623 void GetOutOfBookInfo( char * buf )
10627 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10629 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10630 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10634 if( oob[0] >= 0 || oob[1] >= 0 ) {
10635 for( i=0; i<2; i++ ) {
10639 if( i > 0 && oob[0] >= 0 ) {
10640 strcat( buf, " " );
10643 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10644 sprintf( buf+strlen(buf), "%s%.2f",
10645 pvInfoList[idx].score >= 0 ? "+" : "",
10646 pvInfoList[idx].score / 100.0 );
10652 /* Save game in PGN style and close the file */
10657 int i, offset, linelen, newblock;
10661 int movelen, numlen, blank;
10662 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10664 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10666 tm = time((time_t *) NULL);
10668 PrintPGNTags(f, &gameInfo);
10670 if (backwardMostMove > 0 || startedFromSetupPosition) {
10671 char *fen = PositionToFEN(backwardMostMove, NULL);
10672 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10673 fprintf(f, "\n{--------------\n");
10674 PrintPosition(f, backwardMostMove);
10675 fprintf(f, "--------------}\n");
10679 /* [AS] Out of book annotation */
10680 if( appData.saveOutOfBookInfo ) {
10683 GetOutOfBookInfo( buf );
10685 if( buf[0] != '\0' ) {
10686 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10693 i = backwardMostMove;
10697 while (i < forwardMostMove) {
10698 /* Print comments preceding this move */
10699 if (commentList[i] != NULL) {
10700 if (linelen > 0) fprintf(f, "\n");
10701 fprintf(f, "%s", commentList[i]);
10706 /* Format move number */
10707 if ((i % 2) == 0) {
10708 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10711 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10713 numtext[0] = NULLCHAR;
10716 numlen = strlen(numtext);
10719 /* Print move number */
10720 blank = linelen > 0 && numlen > 0;
10721 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10730 fprintf(f, "%s", numtext);
10734 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10735 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10738 blank = linelen > 0 && movelen > 0;
10739 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10748 fprintf(f, "%s", move_buffer);
10749 linelen += movelen;
10751 /* [AS] Add PV info if present */
10752 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10753 /* [HGM] add time */
10754 char buf[MSG_SIZ]; int seconds;
10756 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10758 if( seconds <= 0) buf[0] = 0; else
10759 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10760 seconds = (seconds + 4)/10; // round to full seconds
10761 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10762 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10765 sprintf( move_buffer, "{%s%.2f/%d%s}",
10766 pvInfoList[i].score >= 0 ? "+" : "",
10767 pvInfoList[i].score / 100.0,
10768 pvInfoList[i].depth,
10771 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10773 /* Print score/depth */
10774 blank = linelen > 0 && movelen > 0;
10775 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10784 fprintf(f, "%s", move_buffer);
10785 linelen += movelen;
10791 /* Start a new line */
10792 if (linelen > 0) fprintf(f, "\n");
10794 /* Print comments after last move */
10795 if (commentList[i] != NULL) {
10796 fprintf(f, "%s\n", commentList[i]);
10800 if (gameInfo.resultDetails != NULL &&
10801 gameInfo.resultDetails[0] != NULLCHAR) {
10802 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10803 PGNResult(gameInfo.result));
10805 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10809 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10813 /* Save game in old style and close the file */
10815 SaveGameOldStyle(f)
10821 tm = time((time_t *) NULL);
10823 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10826 if (backwardMostMove > 0 || startedFromSetupPosition) {
10827 fprintf(f, "\n[--------------\n");
10828 PrintPosition(f, backwardMostMove);
10829 fprintf(f, "--------------]\n");
10834 i = backwardMostMove;
10835 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10837 while (i < forwardMostMove) {
10838 if (commentList[i] != NULL) {
10839 fprintf(f, "[%s]\n", commentList[i]);
10842 if ((i % 2) == 1) {
10843 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10846 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10848 if (commentList[i] != NULL) {
10852 if (i >= forwardMostMove) {
10856 fprintf(f, "%s\n", parseList[i]);
10861 if (commentList[i] != NULL) {
10862 fprintf(f, "[%s]\n", commentList[i]);
10865 /* This isn't really the old style, but it's close enough */
10866 if (gameInfo.resultDetails != NULL &&
10867 gameInfo.resultDetails[0] != NULLCHAR) {
10868 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10869 gameInfo.resultDetails);
10871 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10878 /* Save the current game to open file f and close the file */
10880 SaveGame(f, dummy, dummy2)
10885 if (gameMode == EditPosition) EditPositionDone(TRUE);
10886 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10887 if (appData.oldSaveStyle)
10888 return SaveGameOldStyle(f);
10890 return SaveGamePGN(f);
10893 /* Save the current position to the given file */
10895 SavePositionToFile(filename)
10901 if (strcmp(filename, "-") == 0) {
10902 return SavePosition(stdout, 0, NULL);
10904 f = fopen(filename, "a");
10906 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10907 DisplayError(buf, errno);
10910 SavePosition(f, 0, NULL);
10916 /* Save the current position to the given open file and close the file */
10918 SavePosition(f, dummy, dummy2)
10926 if (gameMode == EditPosition) EditPositionDone(TRUE);
10927 if (appData.oldSaveStyle) {
10928 tm = time((time_t *) NULL);
10930 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10932 fprintf(f, "[--------------\n");
10933 PrintPosition(f, currentMove);
10934 fprintf(f, "--------------]\n");
10936 fen = PositionToFEN(currentMove, NULL);
10937 fprintf(f, "%s\n", fen);
10945 ReloadCmailMsgEvent(unregister)
10949 static char *inFilename = NULL;
10950 static char *outFilename;
10952 struct stat inbuf, outbuf;
10955 /* Any registered moves are unregistered if unregister is set, */
10956 /* i.e. invoked by the signal handler */
10958 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10959 cmailMoveRegistered[i] = FALSE;
10960 if (cmailCommentList[i] != NULL) {
10961 free(cmailCommentList[i]);
10962 cmailCommentList[i] = NULL;
10965 nCmailMovesRegistered = 0;
10968 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10969 cmailResult[i] = CMAIL_NOT_RESULT;
10973 if (inFilename == NULL) {
10974 /* Because the filenames are static they only get malloced once */
10975 /* and they never get freed */
10976 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10977 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10979 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10980 sprintf(outFilename, "%s.out", appData.cmailGameName);
10983 status = stat(outFilename, &outbuf);
10985 cmailMailedMove = FALSE;
10987 status = stat(inFilename, &inbuf);
10988 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10991 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10992 counts the games, notes how each one terminated, etc.
10994 It would be nice to remove this kludge and instead gather all
10995 the information while building the game list. (And to keep it
10996 in the game list nodes instead of having a bunch of fixed-size
10997 parallel arrays.) Note this will require getting each game's
10998 termination from the PGN tags, as the game list builder does
10999 not process the game moves. --mann
11001 cmailMsgLoaded = TRUE;
11002 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11004 /* Load first game in the file or popup game menu */
11005 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11007 #endif /* !WIN32 */
11015 char string[MSG_SIZ];
11017 if ( cmailMailedMove
11018 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11019 return TRUE; /* Allow free viewing */
11022 /* Unregister move to ensure that we don't leave RegisterMove */
11023 /* with the move registered when the conditions for registering no */
11025 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11026 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11027 nCmailMovesRegistered --;
11029 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11031 free(cmailCommentList[lastLoadGameNumber - 1]);
11032 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11036 if (cmailOldMove == -1) {
11037 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11041 if (currentMove > cmailOldMove + 1) {
11042 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11046 if (currentMove < cmailOldMove) {
11047 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11051 if (forwardMostMove > currentMove) {
11052 /* Silently truncate extra moves */
11056 if ( (currentMove == cmailOldMove + 1)
11057 || ( (currentMove == cmailOldMove)
11058 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11059 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11060 if (gameInfo.result != GameUnfinished) {
11061 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11064 if (commentList[currentMove] != NULL) {
11065 cmailCommentList[lastLoadGameNumber - 1]
11066 = StrSave(commentList[currentMove]);
11068 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11070 if (appData.debugMode)
11071 fprintf(debugFP, "Saving %s for game %d\n",
11072 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11075 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11077 f = fopen(string, "w");
11078 if (appData.oldSaveStyle) {
11079 SaveGameOldStyle(f); /* also closes the file */
11081 sprintf(string, "%s.pos.out", appData.cmailGameName);
11082 f = fopen(string, "w");
11083 SavePosition(f, 0, NULL); /* also closes the file */
11085 fprintf(f, "{--------------\n");
11086 PrintPosition(f, currentMove);
11087 fprintf(f, "--------------}\n\n");
11089 SaveGame(f, 0, NULL); /* also closes the file*/
11092 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11093 nCmailMovesRegistered ++;
11094 } else if (nCmailGames == 1) {
11095 DisplayError(_("You have not made a move yet"), 0);
11106 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11107 FILE *commandOutput;
11108 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11109 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11115 if (! cmailMsgLoaded) {
11116 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11120 if (nCmailGames == nCmailResults) {
11121 DisplayError(_("No unfinished games"), 0);
11125 #if CMAIL_PROHIBIT_REMAIL
11126 if (cmailMailedMove) {
11127 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);
11128 DisplayError(msg, 0);
11133 if (! (cmailMailedMove || RegisterMove())) return;
11135 if ( cmailMailedMove
11136 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11137 sprintf(string, partCommandString,
11138 appData.debugMode ? " -v" : "", appData.cmailGameName);
11139 commandOutput = popen(string, "r");
11141 if (commandOutput == NULL) {
11142 DisplayError(_("Failed to invoke cmail"), 0);
11144 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11145 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11147 if (nBuffers > 1) {
11148 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11149 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11150 nBytes = MSG_SIZ - 1;
11152 (void) memcpy(msg, buffer, nBytes);
11154 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11156 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11157 cmailMailedMove = TRUE; /* Prevent >1 moves */
11160 for (i = 0; i < nCmailGames; i ++) {
11161 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11166 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11168 sprintf(buffer, "%s/%s.%s.archive",
11170 appData.cmailGameName,
11172 LoadGameFromFile(buffer, 1, buffer, FALSE);
11173 cmailMsgLoaded = FALSE;
11177 DisplayInformation(msg);
11178 pclose(commandOutput);
11181 if ((*cmailMsg) != '\0') {
11182 DisplayInformation(cmailMsg);
11187 #endif /* !WIN32 */
11196 int prependComma = 0;
11198 char string[MSG_SIZ]; /* Space for game-list */
11201 if (!cmailMsgLoaded) return "";
11203 if (cmailMailedMove) {
11204 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11206 /* Create a list of games left */
11207 sprintf(string, "[");
11208 for (i = 0; i < nCmailGames; i ++) {
11209 if (! ( cmailMoveRegistered[i]
11210 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11211 if (prependComma) {
11212 sprintf(number, ",%d", i + 1);
11214 sprintf(number, "%d", i + 1);
11218 strcat(string, number);
11221 strcat(string, "]");
11223 if (nCmailMovesRegistered + nCmailResults == 0) {
11224 switch (nCmailGames) {
11227 _("Still need to make move for game\n"));
11232 _("Still need to make moves for both games\n"));
11237 _("Still need to make moves for all %d games\n"),
11242 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11245 _("Still need to make a move for game %s\n"),
11250 if (nCmailResults == nCmailGames) {
11251 sprintf(cmailMsg, _("No unfinished games\n"));
11253 sprintf(cmailMsg, _("Ready to send mail\n"));
11259 _("Still need to make moves for games %s\n"),
11271 if (gameMode == Training)
11272 SetTrainingModeOff();
11275 cmailMsgLoaded = FALSE;
11276 if (appData.icsActive) {
11277 SendToICS(ics_prefix);
11278 SendToICS("refresh\n");
11288 /* Give up on clean exit */
11292 /* Keep trying for clean exit */
11296 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11298 if (telnetISR != NULL) {
11299 RemoveInputSource(telnetISR);
11301 if (icsPR != NoProc) {
11302 DestroyChildProcess(icsPR, TRUE);
11305 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11306 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11308 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11309 /* make sure this other one finishes before killing it! */
11310 if(endingGame) { int count = 0;
11311 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11312 while(endingGame && count++ < 10) DoSleep(1);
11313 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11316 /* Kill off chess programs */
11317 if (first.pr != NoProc) {
11320 DoSleep( appData.delayBeforeQuit );
11321 SendToProgram("quit\n", &first);
11322 DoSleep( appData.delayAfterQuit );
11323 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11325 if (second.pr != NoProc) {
11326 DoSleep( appData.delayBeforeQuit );
11327 SendToProgram("quit\n", &second);
11328 DoSleep( appData.delayAfterQuit );
11329 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11331 if (first.isr != NULL) {
11332 RemoveInputSource(first.isr);
11334 if (second.isr != NULL) {
11335 RemoveInputSource(second.isr);
11338 ShutDownFrontEnd();
11345 if (appData.debugMode)
11346 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11350 if (gameMode == MachinePlaysWhite ||
11351 gameMode == MachinePlaysBlack) {
11354 DisplayBothClocks();
11356 if (gameMode == PlayFromGameFile) {
11357 if (appData.timeDelay >= 0)
11358 AutoPlayGameLoop();
11359 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11360 Reset(FALSE, TRUE);
11361 SendToICS(ics_prefix);
11362 SendToICS("refresh\n");
11363 } else if (currentMove < forwardMostMove) {
11364 ForwardInner(forwardMostMove);
11366 pauseExamInvalid = FALSE;
11368 switch (gameMode) {
11372 pauseExamForwardMostMove = forwardMostMove;
11373 pauseExamInvalid = FALSE;
11376 case IcsPlayingWhite:
11377 case IcsPlayingBlack:
11381 case PlayFromGameFile:
11382 (void) StopLoadGameTimer();
11386 case BeginningOfGame:
11387 if (appData.icsActive) return;
11388 /* else fall through */
11389 case MachinePlaysWhite:
11390 case MachinePlaysBlack:
11391 case TwoMachinesPlay:
11392 if (forwardMostMove == 0)
11393 return; /* don't pause if no one has moved */
11394 if ((gameMode == MachinePlaysWhite &&
11395 !WhiteOnMove(forwardMostMove)) ||
11396 (gameMode == MachinePlaysBlack &&
11397 WhiteOnMove(forwardMostMove))) {
11410 char title[MSG_SIZ];
11412 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11413 strcpy(title, _("Edit comment"));
11415 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11416 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11417 parseList[currentMove - 1]);
11420 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11427 char *tags = PGNTags(&gameInfo);
11428 EditTagsPopUp(tags);
11435 if (appData.noChessProgram || gameMode == AnalyzeMode)
11438 if (gameMode != AnalyzeFile) {
11439 if (!appData.icsEngineAnalyze) {
11441 if (gameMode != EditGame) return;
11443 ResurrectChessProgram();
11444 SendToProgram("analyze\n", &first);
11445 first.analyzing = TRUE;
11446 /*first.maybeThinking = TRUE;*/
11447 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11448 EngineOutputPopUp();
11450 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11455 StartAnalysisClock();
11456 GetTimeMark(&lastNodeCountTime);
11463 if (appData.noChessProgram || gameMode == AnalyzeFile)
11466 if (gameMode != AnalyzeMode) {
11468 if (gameMode != EditGame) return;
11469 ResurrectChessProgram();
11470 SendToProgram("analyze\n", &first);
11471 first.analyzing = TRUE;
11472 /*first.maybeThinking = TRUE;*/
11473 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11474 EngineOutputPopUp();
11476 gameMode = AnalyzeFile;
11481 StartAnalysisClock();
11482 GetTimeMark(&lastNodeCountTime);
11487 MachineWhiteEvent()
11490 char *bookHit = NULL;
11492 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11496 if (gameMode == PlayFromGameFile ||
11497 gameMode == TwoMachinesPlay ||
11498 gameMode == Training ||
11499 gameMode == AnalyzeMode ||
11500 gameMode == EndOfGame)
11503 if (gameMode == EditPosition)
11504 EditPositionDone(TRUE);
11506 if (!WhiteOnMove(currentMove)) {
11507 DisplayError(_("It is not White's turn"), 0);
11511 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11514 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11515 gameMode == AnalyzeFile)
11518 ResurrectChessProgram(); /* in case it isn't running */
11519 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11520 gameMode = MachinePlaysWhite;
11523 gameMode = MachinePlaysWhite;
11527 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11529 if (first.sendName) {
11530 sprintf(buf, "name %s\n", gameInfo.black);
11531 SendToProgram(buf, &first);
11533 if (first.sendTime) {
11534 if (first.useColors) {
11535 SendToProgram("black\n", &first); /*gnu kludge*/
11537 SendTimeRemaining(&first, TRUE);
11539 if (first.useColors) {
11540 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11542 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11543 SetMachineThinkingEnables();
11544 first.maybeThinking = TRUE;
11548 if (appData.autoFlipView && !flipView) {
11549 flipView = !flipView;
11550 DrawPosition(FALSE, NULL);
11551 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11554 if(bookHit) { // [HGM] book: simulate book reply
11555 static char bookMove[MSG_SIZ]; // a bit generous?
11557 programStats.nodes = programStats.depth = programStats.time =
11558 programStats.score = programStats.got_only_move = 0;
11559 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11561 strcpy(bookMove, "move ");
11562 strcat(bookMove, bookHit);
11563 HandleMachineMove(bookMove, &first);
11568 MachineBlackEvent()
11571 char *bookHit = NULL;
11573 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11577 if (gameMode == PlayFromGameFile ||
11578 gameMode == TwoMachinesPlay ||
11579 gameMode == Training ||
11580 gameMode == AnalyzeMode ||
11581 gameMode == EndOfGame)
11584 if (gameMode == EditPosition)
11585 EditPositionDone(TRUE);
11587 if (WhiteOnMove(currentMove)) {
11588 DisplayError(_("It is not Black's turn"), 0);
11592 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11595 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11596 gameMode == AnalyzeFile)
11599 ResurrectChessProgram(); /* in case it isn't running */
11600 gameMode = MachinePlaysBlack;
11604 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11606 if (first.sendName) {
11607 sprintf(buf, "name %s\n", gameInfo.white);
11608 SendToProgram(buf, &first);
11610 if (first.sendTime) {
11611 if (first.useColors) {
11612 SendToProgram("white\n", &first); /*gnu kludge*/
11614 SendTimeRemaining(&first, FALSE);
11616 if (first.useColors) {
11617 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11619 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11620 SetMachineThinkingEnables();
11621 first.maybeThinking = TRUE;
11624 if (appData.autoFlipView && flipView) {
11625 flipView = !flipView;
11626 DrawPosition(FALSE, NULL);
11627 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11629 if(bookHit) { // [HGM] book: simulate book reply
11630 static char bookMove[MSG_SIZ]; // a bit generous?
11632 programStats.nodes = programStats.depth = programStats.time =
11633 programStats.score = programStats.got_only_move = 0;
11634 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11636 strcpy(bookMove, "move ");
11637 strcat(bookMove, bookHit);
11638 HandleMachineMove(bookMove, &first);
11644 DisplayTwoMachinesTitle()
11647 if (appData.matchGames > 0) {
11648 if (first.twoMachinesColor[0] == 'w') {
11649 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11650 gameInfo.white, gameInfo.black,
11651 first.matchWins, second.matchWins,
11652 matchGame - 1 - (first.matchWins + second.matchWins));
11654 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11655 gameInfo.white, gameInfo.black,
11656 second.matchWins, first.matchWins,
11657 matchGame - 1 - (first.matchWins + second.matchWins));
11660 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11666 TwoMachinesEvent P((void))
11670 ChessProgramState *onmove;
11671 char *bookHit = NULL;
11673 if (appData.noChessProgram) return;
11675 switch (gameMode) {
11676 case TwoMachinesPlay:
11678 case MachinePlaysWhite:
11679 case MachinePlaysBlack:
11680 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11681 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11685 case BeginningOfGame:
11686 case PlayFromGameFile:
11689 if (gameMode != EditGame) return;
11692 EditPositionDone(TRUE);
11703 // forwardMostMove = currentMove;
11704 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11705 ResurrectChessProgram(); /* in case first program isn't running */
11707 if (second.pr == NULL) {
11708 StartChessProgram(&second);
11709 if (second.protocolVersion == 1) {
11710 TwoMachinesEventIfReady();
11712 /* kludge: allow timeout for initial "feature" command */
11714 DisplayMessage("", _("Starting second chess program"));
11715 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11719 DisplayMessage("", "");
11720 InitChessProgram(&second, FALSE);
11721 SendToProgram("force\n", &second);
11722 if (startedFromSetupPosition) {
11723 SendBoard(&second, backwardMostMove);
11724 if (appData.debugMode) {
11725 fprintf(debugFP, "Two Machines\n");
11728 for (i = backwardMostMove; i < forwardMostMove; i++) {
11729 SendMoveToProgram(i, &second);
11732 gameMode = TwoMachinesPlay;
11736 DisplayTwoMachinesTitle();
11738 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11744 SendToProgram(first.computerString, &first);
11745 if (first.sendName) {
11746 sprintf(buf, "name %s\n", second.tidy);
11747 SendToProgram(buf, &first);
11749 SendToProgram(second.computerString, &second);
11750 if (second.sendName) {
11751 sprintf(buf, "name %s\n", first.tidy);
11752 SendToProgram(buf, &second);
11756 if (!first.sendTime || !second.sendTime) {
11757 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11758 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11760 if (onmove->sendTime) {
11761 if (onmove->useColors) {
11762 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11764 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11766 if (onmove->useColors) {
11767 SendToProgram(onmove->twoMachinesColor, onmove);
11769 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11770 // SendToProgram("go\n", onmove);
11771 onmove->maybeThinking = TRUE;
11772 SetMachineThinkingEnables();
11776 if(bookHit) { // [HGM] book: simulate book reply
11777 static char bookMove[MSG_SIZ]; // a bit generous?
11779 programStats.nodes = programStats.depth = programStats.time =
11780 programStats.score = programStats.got_only_move = 0;
11781 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11783 strcpy(bookMove, "move ");
11784 strcat(bookMove, bookHit);
11785 savedMessage = bookMove; // args for deferred call
11786 savedState = onmove;
11787 ScheduleDelayedEvent(DeferredBookMove, 1);
11794 if (gameMode == Training) {
11795 SetTrainingModeOff();
11796 gameMode = PlayFromGameFile;
11797 DisplayMessage("", _("Training mode off"));
11799 gameMode = Training;
11800 animateTraining = appData.animate;
11802 /* make sure we are not already at the end of the game */
11803 if (currentMove < forwardMostMove) {
11804 SetTrainingModeOn();
11805 DisplayMessage("", _("Training mode on"));
11807 gameMode = PlayFromGameFile;
11808 DisplayError(_("Already at end of game"), 0);
11817 if (!appData.icsActive) return;
11818 switch (gameMode) {
11819 case IcsPlayingWhite:
11820 case IcsPlayingBlack:
11823 case BeginningOfGame:
11831 EditPositionDone(TRUE);
11844 gameMode = IcsIdle;
11855 switch (gameMode) {
11857 SetTrainingModeOff();
11859 case MachinePlaysWhite:
11860 case MachinePlaysBlack:
11861 case BeginningOfGame:
11862 SendToProgram("force\n", &first);
11863 SetUserThinkingEnables();
11865 case PlayFromGameFile:
11866 (void) StopLoadGameTimer();
11867 if (gameFileFP != NULL) {
11872 EditPositionDone(TRUE);
11877 SendToProgram("force\n", &first);
11879 case TwoMachinesPlay:
11880 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11881 ResurrectChessProgram();
11882 SetUserThinkingEnables();
11885 ResurrectChessProgram();
11887 case IcsPlayingBlack:
11888 case IcsPlayingWhite:
11889 DisplayError(_("Warning: You are still playing a game"), 0);
11892 DisplayError(_("Warning: You are still observing a game"), 0);
11895 DisplayError(_("Warning: You are still examining a game"), 0);
11906 first.offeredDraw = second.offeredDraw = 0;
11908 if (gameMode == PlayFromGameFile) {
11909 whiteTimeRemaining = timeRemaining[0][currentMove];
11910 blackTimeRemaining = timeRemaining[1][currentMove];
11914 if (gameMode == MachinePlaysWhite ||
11915 gameMode == MachinePlaysBlack ||
11916 gameMode == TwoMachinesPlay ||
11917 gameMode == EndOfGame) {
11918 i = forwardMostMove;
11919 while (i > currentMove) {
11920 SendToProgram("undo\n", &first);
11923 whiteTimeRemaining = timeRemaining[0][currentMove];
11924 blackTimeRemaining = timeRemaining[1][currentMove];
11925 DisplayBothClocks();
11926 if (whiteFlag || blackFlag) {
11927 whiteFlag = blackFlag = 0;
11932 gameMode = EditGame;
11939 EditPositionEvent()
11941 if (gameMode == EditPosition) {
11947 if (gameMode != EditGame) return;
11949 gameMode = EditPosition;
11952 if (currentMove > 0)
11953 CopyBoard(boards[0], boards[currentMove]);
11955 blackPlaysFirst = !WhiteOnMove(currentMove);
11957 currentMove = forwardMostMove = backwardMostMove = 0;
11958 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11965 /* [DM] icsEngineAnalyze - possible call from other functions */
11966 if (appData.icsEngineAnalyze) {
11967 appData.icsEngineAnalyze = FALSE;
11969 DisplayMessage("",_("Close ICS engine analyze..."));
11971 if (first.analysisSupport && first.analyzing) {
11972 SendToProgram("exit\n", &first);
11973 first.analyzing = FALSE;
11975 thinkOutput[0] = NULLCHAR;
11979 EditPositionDone(Boolean fakeRights)
11981 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11983 startedFromSetupPosition = TRUE;
11984 InitChessProgram(&first, FALSE);
11985 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11986 boards[0][EP_STATUS] = EP_NONE;
11987 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11988 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11989 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11990 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11991 } else boards[0][CASTLING][2] = NoRights;
11992 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11993 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11994 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11995 } else boards[0][CASTLING][5] = NoRights;
11997 SendToProgram("force\n", &first);
11998 if (blackPlaysFirst) {
11999 strcpy(moveList[0], "");
12000 strcpy(parseList[0], "");
12001 currentMove = forwardMostMove = backwardMostMove = 1;
12002 CopyBoard(boards[1], boards[0]);
12004 currentMove = forwardMostMove = backwardMostMove = 0;
12006 SendBoard(&first, forwardMostMove);
12007 if (appData.debugMode) {
12008 fprintf(debugFP, "EditPosDone\n");
12011 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12012 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12013 gameMode = EditGame;
12015 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12016 ClearHighlights(); /* [AS] */
12019 /* Pause for `ms' milliseconds */
12020 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12030 } while (SubtractTimeMarks(&m2, &m1) < ms);
12033 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12035 SendMultiLineToICS(buf)
12038 char temp[MSG_SIZ+1], *p;
12045 strncpy(temp, buf, len);
12050 if (*p == '\n' || *p == '\r')
12055 strcat(temp, "\n");
12057 SendToPlayer(temp, strlen(temp));
12061 SetWhiteToPlayEvent()
12063 if (gameMode == EditPosition) {
12064 blackPlaysFirst = FALSE;
12065 DisplayBothClocks(); /* works because currentMove is 0 */
12066 } else if (gameMode == IcsExamining) {
12067 SendToICS(ics_prefix);
12068 SendToICS("tomove white\n");
12073 SetBlackToPlayEvent()
12075 if (gameMode == EditPosition) {
12076 blackPlaysFirst = TRUE;
12077 currentMove = 1; /* kludge */
12078 DisplayBothClocks();
12080 } else if (gameMode == IcsExamining) {
12081 SendToICS(ics_prefix);
12082 SendToICS("tomove black\n");
12087 EditPositionMenuEvent(selection, x, y)
12088 ChessSquare selection;
12092 ChessSquare piece = boards[0][y][x];
12094 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12096 switch (selection) {
12098 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12099 SendToICS(ics_prefix);
12100 SendToICS("bsetup clear\n");
12101 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12102 SendToICS(ics_prefix);
12103 SendToICS("clearboard\n");
12105 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12106 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12107 for (y = 0; y < BOARD_HEIGHT; y++) {
12108 if (gameMode == IcsExamining) {
12109 if (boards[currentMove][y][x] != EmptySquare) {
12110 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12115 boards[0][y][x] = p;
12120 if (gameMode == EditPosition) {
12121 DrawPosition(FALSE, boards[0]);
12126 SetWhiteToPlayEvent();
12130 SetBlackToPlayEvent();
12134 if (gameMode == IcsExamining) {
12135 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12136 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12139 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12140 if(x == BOARD_LEFT-2) {
12141 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12142 boards[0][y][1] = 0;
12144 if(x == BOARD_RGHT+1) {
12145 if(y >= gameInfo.holdingsSize) break;
12146 boards[0][y][BOARD_WIDTH-2] = 0;
12149 boards[0][y][x] = EmptySquare;
12150 DrawPosition(FALSE, boards[0]);
12155 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12156 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12157 selection = (ChessSquare) (PROMOTED piece);
12158 } else if(piece == EmptySquare) selection = WhiteSilver;
12159 else selection = (ChessSquare)((int)piece - 1);
12163 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12164 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12165 selection = (ChessSquare) (DEMOTED piece);
12166 } else if(piece == EmptySquare) selection = BlackSilver;
12167 else selection = (ChessSquare)((int)piece + 1);
12172 if(gameInfo.variant == VariantShatranj ||
12173 gameInfo.variant == VariantXiangqi ||
12174 gameInfo.variant == VariantCourier ||
12175 gameInfo.variant == VariantMakruk )
12176 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12181 if(gameInfo.variant == VariantXiangqi)
12182 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12183 if(gameInfo.variant == VariantKnightmate)
12184 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12187 if (gameMode == IcsExamining) {
12188 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12189 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12190 PieceToChar(selection), AAA + x, ONE + y);
12193 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12195 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12196 n = PieceToNumber(selection - BlackPawn);
12197 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12198 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12199 boards[0][BOARD_HEIGHT-1-n][1]++;
12201 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12202 n = PieceToNumber(selection);
12203 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12204 boards[0][n][BOARD_WIDTH-1] = selection;
12205 boards[0][n][BOARD_WIDTH-2]++;
12208 boards[0][y][x] = selection;
12209 DrawPosition(TRUE, boards[0]);
12217 DropMenuEvent(selection, x, y)
12218 ChessSquare selection;
12221 ChessMove moveType;
12223 switch (gameMode) {
12224 case IcsPlayingWhite:
12225 case MachinePlaysBlack:
12226 if (!WhiteOnMove(currentMove)) {
12227 DisplayMoveError(_("It is Black's turn"));
12230 moveType = WhiteDrop;
12232 case IcsPlayingBlack:
12233 case MachinePlaysWhite:
12234 if (WhiteOnMove(currentMove)) {
12235 DisplayMoveError(_("It is White's turn"));
12238 moveType = BlackDrop;
12241 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12247 if (moveType == BlackDrop && selection < BlackPawn) {
12248 selection = (ChessSquare) ((int) selection
12249 + (int) BlackPawn - (int) WhitePawn);
12251 if (boards[currentMove][y][x] != EmptySquare) {
12252 DisplayMoveError(_("That square is occupied"));
12256 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12262 /* Accept a pending offer of any kind from opponent */
12264 if (appData.icsActive) {
12265 SendToICS(ics_prefix);
12266 SendToICS("accept\n");
12267 } else if (cmailMsgLoaded) {
12268 if (currentMove == cmailOldMove &&
12269 commentList[cmailOldMove] != NULL &&
12270 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12271 "Black offers a draw" : "White offers a draw")) {
12273 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12274 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12276 DisplayError(_("There is no pending offer on this move"), 0);
12277 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12280 /* Not used for offers from chess program */
12287 /* Decline a pending offer of any kind from opponent */
12289 if (appData.icsActive) {
12290 SendToICS(ics_prefix);
12291 SendToICS("decline\n");
12292 } else if (cmailMsgLoaded) {
12293 if (currentMove == cmailOldMove &&
12294 commentList[cmailOldMove] != NULL &&
12295 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12296 "Black offers a draw" : "White offers a draw")) {
12298 AppendComment(cmailOldMove, "Draw declined", TRUE);
12299 DisplayComment(cmailOldMove - 1, "Draw declined");
12302 DisplayError(_("There is no pending offer on this move"), 0);
12305 /* Not used for offers from chess program */
12312 /* Issue ICS rematch command */
12313 if (appData.icsActive) {
12314 SendToICS(ics_prefix);
12315 SendToICS("rematch\n");
12322 /* Call your opponent's flag (claim a win on time) */
12323 if (appData.icsActive) {
12324 SendToICS(ics_prefix);
12325 SendToICS("flag\n");
12327 switch (gameMode) {
12330 case MachinePlaysWhite:
12333 GameEnds(GameIsDrawn, "Both players ran out of time",
12336 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12338 DisplayError(_("Your opponent is not out of time"), 0);
12341 case MachinePlaysBlack:
12344 GameEnds(GameIsDrawn, "Both players ran out of time",
12347 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12349 DisplayError(_("Your opponent is not out of time"), 0);
12359 /* Offer draw or accept pending draw offer from opponent */
12361 if (appData.icsActive) {
12362 /* Note: tournament rules require draw offers to be
12363 made after you make your move but before you punch
12364 your clock. Currently ICS doesn't let you do that;
12365 instead, you immediately punch your clock after making
12366 a move, but you can offer a draw at any time. */
12368 SendToICS(ics_prefix);
12369 SendToICS("draw\n");
12370 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12371 } else if (cmailMsgLoaded) {
12372 if (currentMove == cmailOldMove &&
12373 commentList[cmailOldMove] != NULL &&
12374 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12375 "Black offers a draw" : "White offers a draw")) {
12376 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12377 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12378 } else if (currentMove == cmailOldMove + 1) {
12379 char *offer = WhiteOnMove(cmailOldMove) ?
12380 "White offers a draw" : "Black offers a draw";
12381 AppendComment(currentMove, offer, TRUE);
12382 DisplayComment(currentMove - 1, offer);
12383 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12385 DisplayError(_("You must make your move before offering a draw"), 0);
12386 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12388 } else if (first.offeredDraw) {
12389 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12391 if (first.sendDrawOffers) {
12392 SendToProgram("draw\n", &first);
12393 userOfferedDraw = TRUE;
12401 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12403 if (appData.icsActive) {
12404 SendToICS(ics_prefix);
12405 SendToICS("adjourn\n");
12407 /* Currently GNU Chess doesn't offer or accept Adjourns */
12415 /* Offer Abort or accept pending Abort offer from opponent */
12417 if (appData.icsActive) {
12418 SendToICS(ics_prefix);
12419 SendToICS("abort\n");
12421 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12428 /* Resign. You can do this even if it's not your turn. */
12430 if (appData.icsActive) {
12431 SendToICS(ics_prefix);
12432 SendToICS("resign\n");
12434 switch (gameMode) {
12435 case MachinePlaysWhite:
12436 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12438 case MachinePlaysBlack:
12439 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12442 if (cmailMsgLoaded) {
12444 if (WhiteOnMove(cmailOldMove)) {
12445 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12447 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12449 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12460 StopObservingEvent()
12462 /* Stop observing current games */
12463 SendToICS(ics_prefix);
12464 SendToICS("unobserve\n");
12468 StopExaminingEvent()
12470 /* Stop observing current game */
12471 SendToICS(ics_prefix);
12472 SendToICS("unexamine\n");
12476 ForwardInner(target)
12481 if (appData.debugMode)
12482 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12483 target, currentMove, forwardMostMove);
12485 if (gameMode == EditPosition)
12488 if (gameMode == PlayFromGameFile && !pausing)
12491 if (gameMode == IcsExamining && pausing)
12492 limit = pauseExamForwardMostMove;
12494 limit = forwardMostMove;
12496 if (target > limit) target = limit;
12498 if (target > 0 && moveList[target - 1][0]) {
12499 int fromX, fromY, toX, toY;
12500 toX = moveList[target - 1][2] - AAA;
12501 toY = moveList[target - 1][3] - ONE;
12502 if (moveList[target - 1][1] == '@') {
12503 if (appData.highlightLastMove) {
12504 SetHighlights(-1, -1, toX, toY);
12507 fromX = moveList[target - 1][0] - AAA;
12508 fromY = moveList[target - 1][1] - ONE;
12509 if (target == currentMove + 1) {
12510 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12512 if (appData.highlightLastMove) {
12513 SetHighlights(fromX, fromY, toX, toY);
12517 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12518 gameMode == Training || gameMode == PlayFromGameFile ||
12519 gameMode == AnalyzeFile) {
12520 while (currentMove < target) {
12521 SendMoveToProgram(currentMove++, &first);
12524 currentMove = target;
12527 if (gameMode == EditGame || gameMode == EndOfGame) {
12528 whiteTimeRemaining = timeRemaining[0][currentMove];
12529 blackTimeRemaining = timeRemaining[1][currentMove];
12531 DisplayBothClocks();
12532 DisplayMove(currentMove - 1);
12533 DrawPosition(FALSE, boards[currentMove]);
12534 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12535 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12536 DisplayComment(currentMove - 1, commentList[currentMove]);
12544 if (gameMode == IcsExamining && !pausing) {
12545 SendToICS(ics_prefix);
12546 SendToICS("forward\n");
12548 ForwardInner(currentMove + 1);
12555 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12556 /* to optimze, we temporarily turn off analysis mode while we feed
12557 * the remaining moves to the engine. Otherwise we get analysis output
12560 if (first.analysisSupport) {
12561 SendToProgram("exit\nforce\n", &first);
12562 first.analyzing = FALSE;
12566 if (gameMode == IcsExamining && !pausing) {
12567 SendToICS(ics_prefix);
12568 SendToICS("forward 999999\n");
12570 ForwardInner(forwardMostMove);
12573 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12574 /* we have fed all the moves, so reactivate analysis mode */
12575 SendToProgram("analyze\n", &first);
12576 first.analyzing = TRUE;
12577 /*first.maybeThinking = TRUE;*/
12578 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12583 BackwardInner(target)
12586 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12588 if (appData.debugMode)
12589 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12590 target, currentMove, forwardMostMove);
12592 if (gameMode == EditPosition) return;
12593 if (currentMove <= backwardMostMove) {
12595 DrawPosition(full_redraw, boards[currentMove]);
12598 if (gameMode == PlayFromGameFile && !pausing)
12601 if (moveList[target][0]) {
12602 int fromX, fromY, toX, toY;
12603 toX = moveList[target][2] - AAA;
12604 toY = moveList[target][3] - ONE;
12605 if (moveList[target][1] == '@') {
12606 if (appData.highlightLastMove) {
12607 SetHighlights(-1, -1, toX, toY);
12610 fromX = moveList[target][0] - AAA;
12611 fromY = moveList[target][1] - ONE;
12612 if (target == currentMove - 1) {
12613 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12615 if (appData.highlightLastMove) {
12616 SetHighlights(fromX, fromY, toX, toY);
12620 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12621 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12622 while (currentMove > target) {
12623 SendToProgram("undo\n", &first);
12627 currentMove = target;
12630 if (gameMode == EditGame || gameMode == EndOfGame) {
12631 whiteTimeRemaining = timeRemaining[0][currentMove];
12632 blackTimeRemaining = timeRemaining[1][currentMove];
12634 DisplayBothClocks();
12635 DisplayMove(currentMove - 1);
12636 DrawPosition(full_redraw, boards[currentMove]);
12637 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12638 // [HGM] PV info: routine tests if comment empty
12639 DisplayComment(currentMove - 1, commentList[currentMove]);
12645 if (gameMode == IcsExamining && !pausing) {
12646 SendToICS(ics_prefix);
12647 SendToICS("backward\n");
12649 BackwardInner(currentMove - 1);
12656 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12657 /* to optimize, we temporarily turn off analysis mode while we undo
12658 * all the moves. Otherwise we get analysis output after each undo.
12660 if (first.analysisSupport) {
12661 SendToProgram("exit\nforce\n", &first);
12662 first.analyzing = FALSE;
12666 if (gameMode == IcsExamining && !pausing) {
12667 SendToICS(ics_prefix);
12668 SendToICS("backward 999999\n");
12670 BackwardInner(backwardMostMove);
12673 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12674 /* we have fed all the moves, so reactivate analysis mode */
12675 SendToProgram("analyze\n", &first);
12676 first.analyzing = TRUE;
12677 /*first.maybeThinking = TRUE;*/
12678 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12685 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12686 if (to >= forwardMostMove) to = forwardMostMove;
12687 if (to <= backwardMostMove) to = backwardMostMove;
12688 if (to < currentMove) {
12696 RevertEvent(Boolean annotate)
12698 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12701 if (gameMode != IcsExamining) {
12702 DisplayError(_("You are not examining a game"), 0);
12706 DisplayError(_("You can't revert while pausing"), 0);
12709 SendToICS(ics_prefix);
12710 SendToICS("revert\n");
12716 switch (gameMode) {
12717 case MachinePlaysWhite:
12718 case MachinePlaysBlack:
12719 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12720 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12723 if (forwardMostMove < 2) return;
12724 currentMove = forwardMostMove = forwardMostMove - 2;
12725 whiteTimeRemaining = timeRemaining[0][currentMove];
12726 blackTimeRemaining = timeRemaining[1][currentMove];
12727 DisplayBothClocks();
12728 DisplayMove(currentMove - 1);
12729 ClearHighlights();/*!! could figure this out*/
12730 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12731 SendToProgram("remove\n", &first);
12732 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12735 case BeginningOfGame:
12739 case IcsPlayingWhite:
12740 case IcsPlayingBlack:
12741 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12742 SendToICS(ics_prefix);
12743 SendToICS("takeback 2\n");
12745 SendToICS(ics_prefix);
12746 SendToICS("takeback 1\n");
12755 ChessProgramState *cps;
12757 switch (gameMode) {
12758 case MachinePlaysWhite:
12759 if (!WhiteOnMove(forwardMostMove)) {
12760 DisplayError(_("It is your turn"), 0);
12765 case MachinePlaysBlack:
12766 if (WhiteOnMove(forwardMostMove)) {
12767 DisplayError(_("It is your turn"), 0);
12772 case TwoMachinesPlay:
12773 if (WhiteOnMove(forwardMostMove) ==
12774 (first.twoMachinesColor[0] == 'w')) {
12780 case BeginningOfGame:
12784 SendToProgram("?\n", cps);
12788 TruncateGameEvent()
12791 if (gameMode != EditGame) return;
12798 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12799 if (forwardMostMove > currentMove) {
12800 if (gameInfo.resultDetails != NULL) {
12801 free(gameInfo.resultDetails);
12802 gameInfo.resultDetails = NULL;
12803 gameInfo.result = GameUnfinished;
12805 forwardMostMove = currentMove;
12806 HistorySet(parseList, backwardMostMove, forwardMostMove,
12814 if (appData.noChessProgram) return;
12815 switch (gameMode) {
12816 case MachinePlaysWhite:
12817 if (WhiteOnMove(forwardMostMove)) {
12818 DisplayError(_("Wait until your turn"), 0);
12822 case BeginningOfGame:
12823 case MachinePlaysBlack:
12824 if (!WhiteOnMove(forwardMostMove)) {
12825 DisplayError(_("Wait until your turn"), 0);
12830 DisplayError(_("No hint available"), 0);
12833 SendToProgram("hint\n", &first);
12834 hintRequested = TRUE;
12840 if (appData.noChessProgram) return;
12841 switch (gameMode) {
12842 case MachinePlaysWhite:
12843 if (WhiteOnMove(forwardMostMove)) {
12844 DisplayError(_("Wait until your turn"), 0);
12848 case BeginningOfGame:
12849 case MachinePlaysBlack:
12850 if (!WhiteOnMove(forwardMostMove)) {
12851 DisplayError(_("Wait until your turn"), 0);
12856 EditPositionDone(TRUE);
12858 case TwoMachinesPlay:
12863 SendToProgram("bk\n", &first);
12864 bookOutput[0] = NULLCHAR;
12865 bookRequested = TRUE;
12871 char *tags = PGNTags(&gameInfo);
12872 TagsPopUp(tags, CmailMsg());
12876 /* end button procedures */
12879 PrintPosition(fp, move)
12885 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12886 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12887 char c = PieceToChar(boards[move][i][j]);
12888 fputc(c == 'x' ? '.' : c, fp);
12889 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12892 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12893 fprintf(fp, "white to play\n");
12895 fprintf(fp, "black to play\n");
12902 if (gameInfo.white != NULL) {
12903 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12909 /* Find last component of program's own name, using some heuristics */
12911 TidyProgramName(prog, host, buf)
12912 char *prog, *host, buf[MSG_SIZ];
12915 int local = (strcmp(host, "localhost") == 0);
12916 while (!local && (p = strchr(prog, ';')) != NULL) {
12918 while (*p == ' ') p++;
12921 if (*prog == '"' || *prog == '\'') {
12922 q = strchr(prog + 1, *prog);
12924 q = strchr(prog, ' ');
12926 if (q == NULL) q = prog + strlen(prog);
12928 while (p >= prog && *p != '/' && *p != '\\') p--;
12930 if(p == prog && *p == '"') p++;
12931 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12932 memcpy(buf, p, q - p);
12933 buf[q - p] = NULLCHAR;
12941 TimeControlTagValue()
12944 if (!appData.clockMode) {
12946 } else if (movesPerSession > 0) {
12947 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12948 } else if (timeIncrement == 0) {
12949 sprintf(buf, "%ld", timeControl/1000);
12951 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12953 return StrSave(buf);
12959 /* This routine is used only for certain modes */
12960 VariantClass v = gameInfo.variant;
12961 ChessMove r = GameUnfinished;
12964 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12965 r = gameInfo.result;
12966 p = gameInfo.resultDetails;
12967 gameInfo.resultDetails = NULL;
12969 ClearGameInfo(&gameInfo);
12970 gameInfo.variant = v;
12972 switch (gameMode) {
12973 case MachinePlaysWhite:
12974 gameInfo.event = StrSave( appData.pgnEventHeader );
12975 gameInfo.site = StrSave(HostName());
12976 gameInfo.date = PGNDate();
12977 gameInfo.round = StrSave("-");
12978 gameInfo.white = StrSave(first.tidy);
12979 gameInfo.black = StrSave(UserName());
12980 gameInfo.timeControl = TimeControlTagValue();
12983 case MachinePlaysBlack:
12984 gameInfo.event = StrSave( appData.pgnEventHeader );
12985 gameInfo.site = StrSave(HostName());
12986 gameInfo.date = PGNDate();
12987 gameInfo.round = StrSave("-");
12988 gameInfo.white = StrSave(UserName());
12989 gameInfo.black = StrSave(first.tidy);
12990 gameInfo.timeControl = TimeControlTagValue();
12993 case TwoMachinesPlay:
12994 gameInfo.event = StrSave( appData.pgnEventHeader );
12995 gameInfo.site = StrSave(HostName());
12996 gameInfo.date = PGNDate();
12997 if (matchGame > 0) {
12999 sprintf(buf, "%d", matchGame);
13000 gameInfo.round = StrSave(buf);
13002 gameInfo.round = StrSave("-");
13004 if (first.twoMachinesColor[0] == 'w') {
13005 gameInfo.white = StrSave(first.tidy);
13006 gameInfo.black = StrSave(second.tidy);
13008 gameInfo.white = StrSave(second.tidy);
13009 gameInfo.black = StrSave(first.tidy);
13011 gameInfo.timeControl = TimeControlTagValue();
13015 gameInfo.event = StrSave("Edited game");
13016 gameInfo.site = StrSave(HostName());
13017 gameInfo.date = PGNDate();
13018 gameInfo.round = StrSave("-");
13019 gameInfo.white = StrSave("-");
13020 gameInfo.black = StrSave("-");
13021 gameInfo.result = r;
13022 gameInfo.resultDetails = p;
13026 gameInfo.event = StrSave("Edited position");
13027 gameInfo.site = StrSave(HostName());
13028 gameInfo.date = PGNDate();
13029 gameInfo.round = StrSave("-");
13030 gameInfo.white = StrSave("-");
13031 gameInfo.black = StrSave("-");
13034 case IcsPlayingWhite:
13035 case IcsPlayingBlack:
13040 case PlayFromGameFile:
13041 gameInfo.event = StrSave("Game from non-PGN file");
13042 gameInfo.site = StrSave(HostName());
13043 gameInfo.date = PGNDate();
13044 gameInfo.round = StrSave("-");
13045 gameInfo.white = StrSave("?");
13046 gameInfo.black = StrSave("?");
13055 ReplaceComment(index, text)
13061 while (*text == '\n') text++;
13062 len = strlen(text);
13063 while (len > 0 && text[len - 1] == '\n') len--;
13065 if (commentList[index] != NULL)
13066 free(commentList[index]);
13069 commentList[index] = NULL;
13072 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13073 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13074 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13075 commentList[index] = (char *) malloc(len + 2);
13076 strncpy(commentList[index], text, len);
13077 commentList[index][len] = '\n';
13078 commentList[index][len + 1] = NULLCHAR;
13080 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13082 commentList[index] = (char *) malloc(len + 6);
13083 strcpy(commentList[index], "{\n");
13084 strncpy(commentList[index]+2, text, len);
13085 commentList[index][len+2] = NULLCHAR;
13086 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13087 strcat(commentList[index], "\n}\n");
13101 if (ch == '\r') continue;
13103 } while (ch != '\0');
13107 AppendComment(index, text, addBraces)
13110 Boolean addBraces; // [HGM] braces: tells if we should add {}
13115 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13116 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13119 while (*text == '\n') text++;
13120 len = strlen(text);
13121 while (len > 0 && text[len - 1] == '\n') len--;
13123 if (len == 0) return;
13125 if (commentList[index] != NULL) {
13126 old = commentList[index];
13127 oldlen = strlen(old);
13128 while(commentList[index][oldlen-1] == '\n')
13129 commentList[index][--oldlen] = NULLCHAR;
13130 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13131 strcpy(commentList[index], old);
13133 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13134 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13135 if(addBraces) addBraces = FALSE; else { text++; len--; }
13136 while (*text == '\n') { text++; len--; }
13137 commentList[index][--oldlen] = NULLCHAR;
13139 if(addBraces) strcat(commentList[index], "\n{\n");
13140 else strcat(commentList[index], "\n");
13141 strcat(commentList[index], text);
13142 if(addBraces) strcat(commentList[index], "\n}\n");
13143 else strcat(commentList[index], "\n");
13145 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13147 strcpy(commentList[index], "{\n");
13148 else commentList[index][0] = NULLCHAR;
13149 strcat(commentList[index], text);
13150 strcat(commentList[index], "\n");
13151 if(addBraces) strcat(commentList[index], "}\n");
13155 static char * FindStr( char * text, char * sub_text )
13157 char * result = strstr( text, sub_text );
13159 if( result != NULL ) {
13160 result += strlen( sub_text );
13166 /* [AS] Try to extract PV info from PGN comment */
13167 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13168 char *GetInfoFromComment( int index, char * text )
13172 if( text != NULL && index > 0 ) {
13175 int time = -1, sec = 0, deci;
13176 char * s_eval = FindStr( text, "[%eval " );
13177 char * s_emt = FindStr( text, "[%emt " );
13179 if( s_eval != NULL || s_emt != NULL ) {
13183 if( s_eval != NULL ) {
13184 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13188 if( delim != ']' ) {
13193 if( s_emt != NULL ) {
13198 /* We expect something like: [+|-]nnn.nn/dd */
13201 if(*text != '{') return text; // [HGM] braces: must be normal comment
13203 sep = strchr( text, '/' );
13204 if( sep == NULL || sep < (text+4) ) {
13208 time = -1; sec = -1; deci = -1;
13209 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13210 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13211 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13212 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13216 if( score_lo < 0 || score_lo >= 100 ) {
13220 if(sec >= 0) time = 600*time + 10*sec; else
13221 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13223 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13225 /* [HGM] PV time: now locate end of PV info */
13226 while( *++sep >= '0' && *sep <= '9'); // strip depth
13228 while( *++sep >= '0' && *sep <= '9'); // strip time
13230 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13232 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13233 while(*sep == ' ') sep++;
13244 pvInfoList[index-1].depth = depth;
13245 pvInfoList[index-1].score = score;
13246 pvInfoList[index-1].time = 10*time; // centi-sec
13247 if(*sep == '}') *sep = 0; else *--sep = '{';
13253 SendToProgram(message, cps)
13255 ChessProgramState *cps;
13257 int count, outCount, error;
13260 if (cps->pr == NULL) return;
13263 if (appData.debugMode) {
13266 fprintf(debugFP, "%ld >%-6s: %s",
13267 SubtractTimeMarks(&now, &programStartTime),
13268 cps->which, message);
13271 count = strlen(message);
13272 outCount = OutputToProcess(cps->pr, message, count, &error);
13273 if (outCount < count && !exiting
13274 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13275 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13276 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13277 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13278 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13279 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13281 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13283 gameInfo.resultDetails = StrSave(buf);
13285 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13290 ReceiveFromProgram(isr, closure, message, count, error)
13291 InputSourceRef isr;
13299 ChessProgramState *cps = (ChessProgramState *)closure;
13301 if (isr != cps->isr) return; /* Killed intentionally */
13305 _("Error: %s chess program (%s) exited unexpectedly"),
13306 cps->which, cps->program);
13307 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13308 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13309 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13310 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13312 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13314 gameInfo.resultDetails = StrSave(buf);
13316 RemoveInputSource(cps->isr);
13317 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13320 _("Error reading from %s chess program (%s)"),
13321 cps->which, cps->program);
13322 RemoveInputSource(cps->isr);
13324 /* [AS] Program is misbehaving badly... kill it */
13325 if( count == -2 ) {
13326 DestroyChildProcess( cps->pr, 9 );
13330 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13335 if ((end_str = strchr(message, '\r')) != NULL)
13336 *end_str = NULLCHAR;
13337 if ((end_str = strchr(message, '\n')) != NULL)
13338 *end_str = NULLCHAR;
13340 if (appData.debugMode) {
13341 TimeMark now; int print = 1;
13342 char *quote = ""; char c; int i;
13344 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13345 char start = message[0];
13346 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13347 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13348 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13349 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13350 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13351 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13352 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13353 sscanf(message, "pong %c", &c)!=1 && start != '#')
13354 { quote = "# "; print = (appData.engineComments == 2); }
13355 message[0] = start; // restore original message
13359 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13360 SubtractTimeMarks(&now, &programStartTime), cps->which,
13366 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13367 if (appData.icsEngineAnalyze) {
13368 if (strstr(message, "whisper") != NULL ||
13369 strstr(message, "kibitz") != NULL ||
13370 strstr(message, "tellics") != NULL) return;
13373 HandleMachineMove(message, cps);
13378 SendTimeControl(cps, mps, tc, inc, sd, st)
13379 ChessProgramState *cps;
13380 int mps, inc, sd, st;
13386 if( timeControl_2 > 0 ) {
13387 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13388 tc = timeControl_2;
13391 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13392 inc /= cps->timeOdds;
13393 st /= cps->timeOdds;
13395 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13398 /* Set exact time per move, normally using st command */
13399 if (cps->stKludge) {
13400 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13402 if (seconds == 0) {
13403 sprintf(buf, "level 1 %d\n", st/60);
13405 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13408 sprintf(buf, "st %d\n", st);
13411 /* Set conventional or incremental time control, using level command */
13412 if (seconds == 0) {
13413 /* Note old gnuchess bug -- minutes:seconds used to not work.
13414 Fixed in later versions, but still avoid :seconds
13415 when seconds is 0. */
13416 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13418 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13419 seconds, inc/1000);
13422 SendToProgram(buf, cps);
13424 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13425 /* Orthogonally, limit search to given depth */
13427 if (cps->sdKludge) {
13428 sprintf(buf, "depth\n%d\n", sd);
13430 sprintf(buf, "sd %d\n", sd);
13432 SendToProgram(buf, cps);
13435 if(cps->nps > 0) { /* [HGM] nps */
13436 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13438 sprintf(buf, "nps %d\n", cps->nps);
13439 SendToProgram(buf, cps);
13444 ChessProgramState *WhitePlayer()
13445 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13447 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13448 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13454 SendTimeRemaining(cps, machineWhite)
13455 ChessProgramState *cps;
13456 int /*boolean*/ machineWhite;
13458 char message[MSG_SIZ];
13461 /* Note: this routine must be called when the clocks are stopped
13462 or when they have *just* been set or switched; otherwise
13463 it will be off by the time since the current tick started.
13465 if (machineWhite) {
13466 time = whiteTimeRemaining / 10;
13467 otime = blackTimeRemaining / 10;
13469 time = blackTimeRemaining / 10;
13470 otime = whiteTimeRemaining / 10;
13472 /* [HGM] translate opponent's time by time-odds factor */
13473 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13474 if (appData.debugMode) {
13475 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13478 if (time <= 0) time = 1;
13479 if (otime <= 0) otime = 1;
13481 sprintf(message, "time %ld\n", time);
13482 SendToProgram(message, cps);
13484 sprintf(message, "otim %ld\n", otime);
13485 SendToProgram(message, cps);
13489 BoolFeature(p, name, loc, cps)
13493 ChessProgramState *cps;
13496 int len = strlen(name);
13498 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13500 sscanf(*p, "%d", &val);
13502 while (**p && **p != ' ') (*p)++;
13503 sprintf(buf, "accepted %s\n", name);
13504 SendToProgram(buf, cps);
13511 IntFeature(p, name, loc, cps)
13515 ChessProgramState *cps;
13518 int len = strlen(name);
13519 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13521 sscanf(*p, "%d", loc);
13522 while (**p && **p != ' ') (*p)++;
13523 sprintf(buf, "accepted %s\n", name);
13524 SendToProgram(buf, cps);
13531 StringFeature(p, name, loc, cps)
13535 ChessProgramState *cps;
13538 int len = strlen(name);
13539 if (strncmp((*p), name, len) == 0
13540 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13542 sscanf(*p, "%[^\"]", loc);
13543 while (**p && **p != '\"') (*p)++;
13544 if (**p == '\"') (*p)++;
13545 sprintf(buf, "accepted %s\n", name);
13546 SendToProgram(buf, cps);
13553 ParseOption(Option *opt, ChessProgramState *cps)
13554 // [HGM] options: process the string that defines an engine option, and determine
13555 // name, type, default value, and allowed value range
13557 char *p, *q, buf[MSG_SIZ];
13558 int n, min = (-1)<<31, max = 1<<31, def;
13560 if(p = strstr(opt->name, " -spin ")) {
13561 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13562 if(max < min) max = min; // enforce consistency
13563 if(def < min) def = min;
13564 if(def > max) def = max;
13569 } else if((p = strstr(opt->name, " -slider "))) {
13570 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13571 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13572 if(max < min) max = min; // enforce consistency
13573 if(def < min) def = min;
13574 if(def > max) def = max;
13578 opt->type = Spin; // Slider;
13579 } else if((p = strstr(opt->name, " -string "))) {
13580 opt->textValue = p+9;
13581 opt->type = TextBox;
13582 } else if((p = strstr(opt->name, " -file "))) {
13583 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13584 opt->textValue = p+7;
13585 opt->type = TextBox; // FileName;
13586 } else if((p = strstr(opt->name, " -path "))) {
13587 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13588 opt->textValue = p+7;
13589 opt->type = TextBox; // PathName;
13590 } else if(p = strstr(opt->name, " -check ")) {
13591 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13592 opt->value = (def != 0);
13593 opt->type = CheckBox;
13594 } else if(p = strstr(opt->name, " -combo ")) {
13595 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13596 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13597 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13598 opt->value = n = 0;
13599 while(q = StrStr(q, " /// ")) {
13600 n++; *q = 0; // count choices, and null-terminate each of them
13602 if(*q == '*') { // remember default, which is marked with * prefix
13606 cps->comboList[cps->comboCnt++] = q;
13608 cps->comboList[cps->comboCnt++] = NULL;
13610 opt->type = ComboBox;
13611 } else if(p = strstr(opt->name, " -button")) {
13612 opt->type = Button;
13613 } else if(p = strstr(opt->name, " -save")) {
13614 opt->type = SaveButton;
13615 } else return FALSE;
13616 *p = 0; // terminate option name
13617 // now look if the command-line options define a setting for this engine option.
13618 if(cps->optionSettings && cps->optionSettings[0])
13619 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13620 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13621 sprintf(buf, "option %s", p);
13622 if(p = strstr(buf, ",")) *p = 0;
13624 SendToProgram(buf, cps);
13630 FeatureDone(cps, val)
13631 ChessProgramState* cps;
13634 DelayedEventCallback cb = GetDelayedEvent();
13635 if ((cb == InitBackEnd3 && cps == &first) ||
13636 (cb == TwoMachinesEventIfReady && cps == &second)) {
13637 CancelDelayedEvent();
13638 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13640 cps->initDone = val;
13643 /* Parse feature command from engine */
13645 ParseFeatures(args, cps)
13647 ChessProgramState *cps;
13655 while (*p == ' ') p++;
13656 if (*p == NULLCHAR) return;
13658 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13659 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13660 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13661 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13662 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13663 if (BoolFeature(&p, "reuse", &val, cps)) {
13664 /* Engine can disable reuse, but can't enable it if user said no */
13665 if (!val) cps->reuse = FALSE;
13668 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13669 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13670 if (gameMode == TwoMachinesPlay) {
13671 DisplayTwoMachinesTitle();
13677 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13678 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13679 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13680 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13681 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13682 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13683 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13684 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13685 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13686 if (IntFeature(&p, "done", &val, cps)) {
13687 FeatureDone(cps, val);
13690 /* Added by Tord: */
13691 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13692 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13693 /* End of additions by Tord */
13695 /* [HGM] added features: */
13696 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13697 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13698 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13699 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13700 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13701 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13702 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13703 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13704 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13705 SendToProgram(buf, cps);
13708 if(cps->nrOptions >= MAX_OPTIONS) {
13710 sprintf(buf, "%s engine has too many options\n", cps->which);
13711 DisplayError(buf, 0);
13715 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13716 /* End of additions by HGM */
13718 /* unknown feature: complain and skip */
13720 while (*q && *q != '=') q++;
13721 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13722 SendToProgram(buf, cps);
13728 while (*p && *p != '\"') p++;
13729 if (*p == '\"') p++;
13731 while (*p && *p != ' ') p++;
13739 PeriodicUpdatesEvent(newState)
13742 if (newState == appData.periodicUpdates)
13745 appData.periodicUpdates=newState;
13747 /* Display type changes, so update it now */
13748 // DisplayAnalysis();
13750 /* Get the ball rolling again... */
13752 AnalysisPeriodicEvent(1);
13753 StartAnalysisClock();
13758 PonderNextMoveEvent(newState)
13761 if (newState == appData.ponderNextMove) return;
13762 if (gameMode == EditPosition) EditPositionDone(TRUE);
13764 SendToProgram("hard\n", &first);
13765 if (gameMode == TwoMachinesPlay) {
13766 SendToProgram("hard\n", &second);
13769 SendToProgram("easy\n", &first);
13770 thinkOutput[0] = NULLCHAR;
13771 if (gameMode == TwoMachinesPlay) {
13772 SendToProgram("easy\n", &second);
13775 appData.ponderNextMove = newState;
13779 NewSettingEvent(option, command, value)
13785 if (gameMode == EditPosition) EditPositionDone(TRUE);
13786 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13787 SendToProgram(buf, &first);
13788 if (gameMode == TwoMachinesPlay) {
13789 SendToProgram(buf, &second);
13794 ShowThinkingEvent()
13795 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13797 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13798 int newState = appData.showThinking
13799 // [HGM] thinking: other features now need thinking output as well
13800 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13802 if (oldState == newState) return;
13803 oldState = newState;
13804 if (gameMode == EditPosition) EditPositionDone(TRUE);
13806 SendToProgram("post\n", &first);
13807 if (gameMode == TwoMachinesPlay) {
13808 SendToProgram("post\n", &second);
13811 SendToProgram("nopost\n", &first);
13812 thinkOutput[0] = NULLCHAR;
13813 if (gameMode == TwoMachinesPlay) {
13814 SendToProgram("nopost\n", &second);
13817 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13821 AskQuestionEvent(title, question, replyPrefix, which)
13822 char *title; char *question; char *replyPrefix; char *which;
13824 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13825 if (pr == NoProc) return;
13826 AskQuestion(title, question, replyPrefix, pr);
13830 DisplayMove(moveNumber)
13833 char message[MSG_SIZ];
13835 char cpThinkOutput[MSG_SIZ];
13837 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13839 if (moveNumber == forwardMostMove - 1 ||
13840 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13842 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13844 if (strchr(cpThinkOutput, '\n')) {
13845 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13848 *cpThinkOutput = NULLCHAR;
13851 /* [AS] Hide thinking from human user */
13852 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13853 *cpThinkOutput = NULLCHAR;
13854 if( thinkOutput[0] != NULLCHAR ) {
13857 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13858 cpThinkOutput[i] = '.';
13860 cpThinkOutput[i] = NULLCHAR;
13861 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13865 if (moveNumber == forwardMostMove - 1 &&
13866 gameInfo.resultDetails != NULL) {
13867 if (gameInfo.resultDetails[0] == NULLCHAR) {
13868 sprintf(res, " %s", PGNResult(gameInfo.result));
13870 sprintf(res, " {%s} %s",
13871 gameInfo.resultDetails, PGNResult(gameInfo.result));
13877 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13878 DisplayMessage(res, cpThinkOutput);
13880 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13881 WhiteOnMove(moveNumber) ? " " : ".. ",
13882 parseList[moveNumber], res);
13883 DisplayMessage(message, cpThinkOutput);
13888 DisplayComment(moveNumber, text)
13892 char title[MSG_SIZ];
13893 char buf[8000]; // comment can be long!
13896 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13897 strcpy(title, "Comment");
13899 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13900 WhiteOnMove(moveNumber) ? " " : ".. ",
13901 parseList[moveNumber]);
13903 // [HGM] PV info: display PV info together with (or as) comment
13904 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13905 if(text == NULL) text = "";
13906 score = pvInfoList[moveNumber].score;
13907 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13908 depth, (pvInfoList[moveNumber].time+50)/100, text);
13911 if (text != NULL && (appData.autoDisplayComment || commentUp))
13912 CommentPopUp(title, text);
13915 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13916 * might be busy thinking or pondering. It can be omitted if your
13917 * gnuchess is configured to stop thinking immediately on any user
13918 * input. However, that gnuchess feature depends on the FIONREAD
13919 * ioctl, which does not work properly on some flavors of Unix.
13923 ChessProgramState *cps;
13926 if (!cps->useSigint) return;
13927 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13928 switch (gameMode) {
13929 case MachinePlaysWhite:
13930 case MachinePlaysBlack:
13931 case TwoMachinesPlay:
13932 case IcsPlayingWhite:
13933 case IcsPlayingBlack:
13936 /* Skip if we know it isn't thinking */
13937 if (!cps->maybeThinking) return;
13938 if (appData.debugMode)
13939 fprintf(debugFP, "Interrupting %s\n", cps->which);
13940 InterruptChildProcess(cps->pr);
13941 cps->maybeThinking = FALSE;
13946 #endif /*ATTENTION*/
13952 if (whiteTimeRemaining <= 0) {
13955 if (appData.icsActive) {
13956 if (appData.autoCallFlag &&
13957 gameMode == IcsPlayingBlack && !blackFlag) {
13958 SendToICS(ics_prefix);
13959 SendToICS("flag\n");
13963 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13965 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13966 if (appData.autoCallFlag) {
13967 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13974 if (blackTimeRemaining <= 0) {
13977 if (appData.icsActive) {
13978 if (appData.autoCallFlag &&
13979 gameMode == IcsPlayingWhite && !whiteFlag) {
13980 SendToICS(ics_prefix);
13981 SendToICS("flag\n");
13985 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13987 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13988 if (appData.autoCallFlag) {
13989 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14002 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14003 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14006 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14008 if ( !WhiteOnMove(forwardMostMove) )
14009 /* White made time control */
14010 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14011 /* [HGM] time odds: correct new time quota for time odds! */
14012 / WhitePlayer()->timeOdds;
14014 /* Black made time control */
14015 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14016 / WhitePlayer()->other->timeOdds;
14020 DisplayBothClocks()
14022 int wom = gameMode == EditPosition ?
14023 !blackPlaysFirst : WhiteOnMove(currentMove);
14024 DisplayWhiteClock(whiteTimeRemaining, wom);
14025 DisplayBlackClock(blackTimeRemaining, !wom);
14029 /* Timekeeping seems to be a portability nightmare. I think everyone
14030 has ftime(), but I'm really not sure, so I'm including some ifdefs
14031 to use other calls if you don't. Clocks will be less accurate if
14032 you have neither ftime nor gettimeofday.
14035 /* VS 2008 requires the #include outside of the function */
14036 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14037 #include <sys/timeb.h>
14040 /* Get the current time as a TimeMark */
14045 #if HAVE_GETTIMEOFDAY
14047 struct timeval timeVal;
14048 struct timezone timeZone;
14050 gettimeofday(&timeVal, &timeZone);
14051 tm->sec = (long) timeVal.tv_sec;
14052 tm->ms = (int) (timeVal.tv_usec / 1000L);
14054 #else /*!HAVE_GETTIMEOFDAY*/
14057 // include <sys/timeb.h> / moved to just above start of function
14058 struct timeb timeB;
14061 tm->sec = (long) timeB.time;
14062 tm->ms = (int) timeB.millitm;
14064 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14065 tm->sec = (long) time(NULL);
14071 /* Return the difference in milliseconds between two
14072 time marks. We assume the difference will fit in a long!
14075 SubtractTimeMarks(tm2, tm1)
14076 TimeMark *tm2, *tm1;
14078 return 1000L*(tm2->sec - tm1->sec) +
14079 (long) (tm2->ms - tm1->ms);
14084 * Code to manage the game clocks.
14086 * In tournament play, black starts the clock and then white makes a move.
14087 * We give the human user a slight advantage if he is playing white---the
14088 * clocks don't run until he makes his first move, so it takes zero time.
14089 * Also, we don't account for network lag, so we could get out of sync
14090 * with GNU Chess's clock -- but then, referees are always right.
14093 static TimeMark tickStartTM;
14094 static long intendedTickLength;
14097 NextTickLength(timeRemaining)
14098 long timeRemaining;
14100 long nominalTickLength, nextTickLength;
14102 if (timeRemaining > 0L && timeRemaining <= 10000L)
14103 nominalTickLength = 100L;
14105 nominalTickLength = 1000L;
14106 nextTickLength = timeRemaining % nominalTickLength;
14107 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14109 return nextTickLength;
14112 /* Adjust clock one minute up or down */
14114 AdjustClock(Boolean which, int dir)
14116 if(which) blackTimeRemaining += 60000*dir;
14117 else whiteTimeRemaining += 60000*dir;
14118 DisplayBothClocks();
14121 /* Stop clocks and reset to a fresh time control */
14125 (void) StopClockTimer();
14126 if (appData.icsActive) {
14127 whiteTimeRemaining = blackTimeRemaining = 0;
14128 } else if (searchTime) {
14129 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14130 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14131 } else { /* [HGM] correct new time quote for time odds */
14132 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14133 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14135 if (whiteFlag || blackFlag) {
14137 whiteFlag = blackFlag = FALSE;
14139 DisplayBothClocks();
14142 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14144 /* Decrement running clock by amount of time that has passed */
14148 long timeRemaining;
14149 long lastTickLength, fudge;
14152 if (!appData.clockMode) return;
14153 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14157 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14159 /* Fudge if we woke up a little too soon */
14160 fudge = intendedTickLength - lastTickLength;
14161 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14163 if (WhiteOnMove(forwardMostMove)) {
14164 if(whiteNPS >= 0) lastTickLength = 0;
14165 timeRemaining = whiteTimeRemaining -= lastTickLength;
14166 DisplayWhiteClock(whiteTimeRemaining - fudge,
14167 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14169 if(blackNPS >= 0) lastTickLength = 0;
14170 timeRemaining = blackTimeRemaining -= lastTickLength;
14171 DisplayBlackClock(blackTimeRemaining - fudge,
14172 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14175 if (CheckFlags()) return;
14178 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14179 StartClockTimer(intendedTickLength);
14181 /* if the time remaining has fallen below the alarm threshold, sound the
14182 * alarm. if the alarm has sounded and (due to a takeback or time control
14183 * with increment) the time remaining has increased to a level above the
14184 * threshold, reset the alarm so it can sound again.
14187 if (appData.icsActive && appData.icsAlarm) {
14189 /* make sure we are dealing with the user's clock */
14190 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14191 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14194 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14195 alarmSounded = FALSE;
14196 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14198 alarmSounded = TRUE;
14204 /* A player has just moved, so stop the previously running
14205 clock and (if in clock mode) start the other one.
14206 We redisplay both clocks in case we're in ICS mode, because
14207 ICS gives us an update to both clocks after every move.
14208 Note that this routine is called *after* forwardMostMove
14209 is updated, so the last fractional tick must be subtracted
14210 from the color that is *not* on move now.
14213 SwitchClocks(int newMoveNr)
14215 long lastTickLength;
14217 int flagged = FALSE;
14221 if (StopClockTimer() && appData.clockMode) {
14222 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14223 if (!WhiteOnMove(forwardMostMove)) {
14224 if(blackNPS >= 0) lastTickLength = 0;
14225 blackTimeRemaining -= lastTickLength;
14226 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14227 // if(pvInfoList[forwardMostMove-1].time == -1)
14228 pvInfoList[forwardMostMove-1].time = // use GUI time
14229 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14231 if(whiteNPS >= 0) lastTickLength = 0;
14232 whiteTimeRemaining -= lastTickLength;
14233 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14234 // if(pvInfoList[forwardMostMove-1].time == -1)
14235 pvInfoList[forwardMostMove-1].time =
14236 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14238 flagged = CheckFlags();
14240 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14241 CheckTimeControl();
14243 if (flagged || !appData.clockMode) return;
14245 switch (gameMode) {
14246 case MachinePlaysBlack:
14247 case MachinePlaysWhite:
14248 case BeginningOfGame:
14249 if (pausing) return;
14253 case PlayFromGameFile:
14261 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14262 if(WhiteOnMove(forwardMostMove))
14263 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14264 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14268 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14269 whiteTimeRemaining : blackTimeRemaining);
14270 StartClockTimer(intendedTickLength);
14274 /* Stop both clocks */
14278 long lastTickLength;
14281 if (!StopClockTimer()) return;
14282 if (!appData.clockMode) return;
14286 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14287 if (WhiteOnMove(forwardMostMove)) {
14288 if(whiteNPS >= 0) lastTickLength = 0;
14289 whiteTimeRemaining -= lastTickLength;
14290 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14292 if(blackNPS >= 0) lastTickLength = 0;
14293 blackTimeRemaining -= lastTickLength;
14294 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14299 /* Start clock of player on move. Time may have been reset, so
14300 if clock is already running, stop and restart it. */
14304 (void) StopClockTimer(); /* in case it was running already */
14305 DisplayBothClocks();
14306 if (CheckFlags()) return;
14308 if (!appData.clockMode) return;
14309 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14311 GetTimeMark(&tickStartTM);
14312 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14313 whiteTimeRemaining : blackTimeRemaining);
14315 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14316 whiteNPS = blackNPS = -1;
14317 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14318 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14319 whiteNPS = first.nps;
14320 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14321 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14322 blackNPS = first.nps;
14323 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14324 whiteNPS = second.nps;
14325 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14326 blackNPS = second.nps;
14327 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14329 StartClockTimer(intendedTickLength);
14336 long second, minute, hour, day;
14338 static char buf[32];
14340 if (ms > 0 && ms <= 9900) {
14341 /* convert milliseconds to tenths, rounding up */
14342 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14344 sprintf(buf, " %03.1f ", tenths/10.0);
14348 /* convert milliseconds to seconds, rounding up */
14349 /* use floating point to avoid strangeness of integer division
14350 with negative dividends on many machines */
14351 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14358 day = second / (60 * 60 * 24);
14359 second = second % (60 * 60 * 24);
14360 hour = second / (60 * 60);
14361 second = second % (60 * 60);
14362 minute = second / 60;
14363 second = second % 60;
14366 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14367 sign, day, hour, minute, second);
14369 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14371 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14378 * This is necessary because some C libraries aren't ANSI C compliant yet.
14381 StrStr(string, match)
14382 char *string, *match;
14386 length = strlen(match);
14388 for (i = strlen(string) - length; i >= 0; i--, string++)
14389 if (!strncmp(match, string, length))
14396 StrCaseStr(string, match)
14397 char *string, *match;
14401 length = strlen(match);
14403 for (i = strlen(string) - length; i >= 0; i--, string++) {
14404 for (j = 0; j < length; j++) {
14405 if (ToLower(match[j]) != ToLower(string[j]))
14408 if (j == length) return string;
14422 c1 = ToLower(*s1++);
14423 c2 = ToLower(*s2++);
14424 if (c1 > c2) return 1;
14425 if (c1 < c2) return -1;
14426 if (c1 == NULLCHAR) return 0;
14435 return isupper(c) ? tolower(c) : c;
14443 return islower(c) ? toupper(c) : c;
14445 #endif /* !_amigados */
14453 if ((ret = (char *) malloc(strlen(s) + 1))) {
14460 StrSavePtr(s, savePtr)
14461 char *s, **savePtr;
14466 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14467 strcpy(*savePtr, s);
14479 clock = time((time_t *)NULL);
14480 tm = localtime(&clock);
14481 sprintf(buf, "%04d.%02d.%02d",
14482 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14483 return StrSave(buf);
14488 PositionToFEN(move, overrideCastling)
14490 char *overrideCastling;
14492 int i, j, fromX, fromY, toX, toY;
14499 whiteToPlay = (gameMode == EditPosition) ?
14500 !blackPlaysFirst : (move % 2 == 0);
14503 /* Piece placement data */
14504 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14506 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14507 if (boards[move][i][j] == EmptySquare) {
14509 } else { ChessSquare piece = boards[move][i][j];
14510 if (emptycount > 0) {
14511 if(emptycount<10) /* [HGM] can be >= 10 */
14512 *p++ = '0' + emptycount;
14513 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14516 if(PieceToChar(piece) == '+') {
14517 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14519 piece = (ChessSquare)(DEMOTED piece);
14521 *p++ = PieceToChar(piece);
14523 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14524 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14529 if (emptycount > 0) {
14530 if(emptycount<10) /* [HGM] can be >= 10 */
14531 *p++ = '0' + emptycount;
14532 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14539 /* [HGM] print Crazyhouse or Shogi holdings */
14540 if( gameInfo.holdingsWidth ) {
14541 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14543 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14544 piece = boards[move][i][BOARD_WIDTH-1];
14545 if( piece != EmptySquare )
14546 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14547 *p++ = PieceToChar(piece);
14549 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14550 piece = boards[move][BOARD_HEIGHT-i-1][0];
14551 if( piece != EmptySquare )
14552 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14553 *p++ = PieceToChar(piece);
14556 if( q == p ) *p++ = '-';
14562 *p++ = whiteToPlay ? 'w' : 'b';
14565 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14566 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14568 if(nrCastlingRights) {
14570 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14571 /* [HGM] write directly from rights */
14572 if(boards[move][CASTLING][2] != NoRights &&
14573 boards[move][CASTLING][0] != NoRights )
14574 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14575 if(boards[move][CASTLING][2] != NoRights &&
14576 boards[move][CASTLING][1] != NoRights )
14577 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14578 if(boards[move][CASTLING][5] != NoRights &&
14579 boards[move][CASTLING][3] != NoRights )
14580 *p++ = boards[move][CASTLING][3] + AAA;
14581 if(boards[move][CASTLING][5] != NoRights &&
14582 boards[move][CASTLING][4] != NoRights )
14583 *p++ = boards[move][CASTLING][4] + AAA;
14586 /* [HGM] write true castling rights */
14587 if( nrCastlingRights == 6 ) {
14588 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14589 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14590 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14591 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14592 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14593 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14594 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14595 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14598 if (q == p) *p++ = '-'; /* No castling rights */
14602 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14603 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14604 /* En passant target square */
14605 if (move > backwardMostMove) {
14606 fromX = moveList[move - 1][0] - AAA;
14607 fromY = moveList[move - 1][1] - ONE;
14608 toX = moveList[move - 1][2] - AAA;
14609 toY = moveList[move - 1][3] - ONE;
14610 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14611 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14612 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14614 /* 2-square pawn move just happened */
14616 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14620 } else if(move == backwardMostMove) {
14621 // [HGM] perhaps we should always do it like this, and forget the above?
14622 if((signed char)boards[move][EP_STATUS] >= 0) {
14623 *p++ = boards[move][EP_STATUS] + AAA;
14624 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14635 /* [HGM] find reversible plies */
14636 { int i = 0, j=move;
14638 if (appData.debugMode) { int k;
14639 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14640 for(k=backwardMostMove; k<=forwardMostMove; k++)
14641 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14645 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14646 if( j == backwardMostMove ) i += initialRulePlies;
14647 sprintf(p, "%d ", i);
14648 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14650 /* Fullmove number */
14651 sprintf(p, "%d", (move / 2) + 1);
14653 return StrSave(buf);
14657 ParseFEN(board, blackPlaysFirst, fen)
14659 int *blackPlaysFirst;
14669 /* [HGM] by default clear Crazyhouse holdings, if present */
14670 if(gameInfo.holdingsWidth) {
14671 for(i=0; i<BOARD_HEIGHT; i++) {
14672 board[i][0] = EmptySquare; /* black holdings */
14673 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14674 board[i][1] = (ChessSquare) 0; /* black counts */
14675 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14679 /* Piece placement data */
14680 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14683 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14684 if (*p == '/') p++;
14685 emptycount = gameInfo.boardWidth - j;
14686 while (emptycount--)
14687 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14689 #if(BOARD_FILES >= 10)
14690 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14691 p++; emptycount=10;
14692 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14693 while (emptycount--)
14694 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14696 } else if (isdigit(*p)) {
14697 emptycount = *p++ - '0';
14698 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14699 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14700 while (emptycount--)
14701 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14702 } else if (*p == '+' || isalpha(*p)) {
14703 if (j >= gameInfo.boardWidth) return FALSE;
14705 piece = CharToPiece(*++p);
14706 if(piece == EmptySquare) return FALSE; /* unknown piece */
14707 piece = (ChessSquare) (PROMOTED piece ); p++;
14708 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14709 } else piece = CharToPiece(*p++);
14711 if(piece==EmptySquare) return FALSE; /* unknown piece */
14712 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14713 piece = (ChessSquare) (PROMOTED piece);
14714 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14717 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14723 while (*p == '/' || *p == ' ') p++;
14725 /* [HGM] look for Crazyhouse holdings here */
14726 while(*p==' ') p++;
14727 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14729 if(*p == '-' ) *p++; /* empty holdings */ else {
14730 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14731 /* if we would allow FEN reading to set board size, we would */
14732 /* have to add holdings and shift the board read so far here */
14733 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14735 if((int) piece >= (int) BlackPawn ) {
14736 i = (int)piece - (int)BlackPawn;
14737 i = PieceToNumber((ChessSquare)i);
14738 if( i >= gameInfo.holdingsSize ) return FALSE;
14739 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14740 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14742 i = (int)piece - (int)WhitePawn;
14743 i = PieceToNumber((ChessSquare)i);
14744 if( i >= gameInfo.holdingsSize ) return FALSE;
14745 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14746 board[i][BOARD_WIDTH-2]++; /* black holdings */
14750 if(*p == ']') *p++;
14753 while(*p == ' ') p++;
14758 *blackPlaysFirst = FALSE;
14761 *blackPlaysFirst = TRUE;
14767 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14768 /* return the extra info in global variiables */
14770 /* set defaults in case FEN is incomplete */
14771 board[EP_STATUS] = EP_UNKNOWN;
14772 for(i=0; i<nrCastlingRights; i++ ) {
14773 board[CASTLING][i] =
14774 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14775 } /* assume possible unless obviously impossible */
14776 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14777 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14778 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14779 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14780 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14781 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14782 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14783 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14786 while(*p==' ') p++;
14787 if(nrCastlingRights) {
14788 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14789 /* castling indicator present, so default becomes no castlings */
14790 for(i=0; i<nrCastlingRights; i++ ) {
14791 board[CASTLING][i] = NoRights;
14794 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14795 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14796 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14797 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14798 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14800 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14801 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14802 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14804 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14805 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14806 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14807 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14808 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14809 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14812 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14813 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14814 board[CASTLING][2] = whiteKingFile;
14817 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14818 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14819 board[CASTLING][2] = whiteKingFile;
14822 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14823 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14824 board[CASTLING][5] = blackKingFile;
14827 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14828 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14829 board[CASTLING][5] = blackKingFile;
14832 default: /* FRC castlings */
14833 if(c >= 'a') { /* black rights */
14834 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14835 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14836 if(i == BOARD_RGHT) break;
14837 board[CASTLING][5] = i;
14839 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14840 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14842 board[CASTLING][3] = c;
14844 board[CASTLING][4] = c;
14845 } else { /* white rights */
14846 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14847 if(board[0][i] == WhiteKing) break;
14848 if(i == BOARD_RGHT) break;
14849 board[CASTLING][2] = i;
14850 c -= AAA - 'a' + 'A';
14851 if(board[0][c] >= WhiteKing) break;
14853 board[CASTLING][0] = c;
14855 board[CASTLING][1] = c;
14859 for(i=0; i<nrCastlingRights; i++)
14860 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14861 if (appData.debugMode) {
14862 fprintf(debugFP, "FEN castling rights:");
14863 for(i=0; i<nrCastlingRights; i++)
14864 fprintf(debugFP, " %d", board[CASTLING][i]);
14865 fprintf(debugFP, "\n");
14868 while(*p==' ') p++;
14871 /* read e.p. field in games that know e.p. capture */
14872 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14873 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14875 p++; board[EP_STATUS] = EP_NONE;
14877 char c = *p++ - AAA;
14879 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14880 if(*p >= '0' && *p <='9') *p++;
14881 board[EP_STATUS] = c;
14886 if(sscanf(p, "%d", &i) == 1) {
14887 FENrulePlies = i; /* 50-move ply counter */
14888 /* (The move number is still ignored) */
14895 EditPositionPasteFEN(char *fen)
14898 Board initial_position;
14900 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14901 DisplayError(_("Bad FEN position in clipboard"), 0);
14904 int savedBlackPlaysFirst = blackPlaysFirst;
14905 EditPositionEvent();
14906 blackPlaysFirst = savedBlackPlaysFirst;
14907 CopyBoard(boards[0], initial_position);
14908 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14909 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14910 DisplayBothClocks();
14911 DrawPosition(FALSE, boards[currentMove]);
14916 static char cseq[12] = "\\ ";
14918 Boolean set_cont_sequence(char *new_seq)
14923 // handle bad attempts to set the sequence
14925 return 0; // acceptable error - no debug
14927 len = strlen(new_seq);
14928 ret = (len > 0) && (len < sizeof(cseq));
14930 strcpy(cseq, new_seq);
14931 else if (appData.debugMode)
14932 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14937 reformat a source message so words don't cross the width boundary. internal
14938 newlines are not removed. returns the wrapped size (no null character unless
14939 included in source message). If dest is NULL, only calculate the size required
14940 for the dest buffer. lp argument indicats line position upon entry, and it's
14941 passed back upon exit.
14943 int wrap(char *dest, char *src, int count, int width, int *lp)
14945 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14947 cseq_len = strlen(cseq);
14948 old_line = line = *lp;
14949 ansi = len = clen = 0;
14951 for (i=0; i < count; i++)
14953 if (src[i] == '\033')
14956 // if we hit the width, back up
14957 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14959 // store i & len in case the word is too long
14960 old_i = i, old_len = len;
14962 // find the end of the last word
14963 while (i && src[i] != ' ' && src[i] != '\n')
14969 // word too long? restore i & len before splitting it
14970 if ((old_i-i+clen) >= width)
14977 if (i && src[i-1] == ' ')
14980 if (src[i] != ' ' && src[i] != '\n')
14987 // now append the newline and continuation sequence
14992 strncpy(dest+len, cseq, cseq_len);
15000 dest[len] = src[i];
15004 if (src[i] == '\n')
15009 if (dest && appData.debugMode)
15011 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15012 count, width, line, len, *lp);
15013 show_bytes(debugFP, src, count);
15014 fprintf(debugFP, "\ndest: ");
15015 show_bytes(debugFP, dest, len);
15016 fprintf(debugFP, "\n");
15018 *lp = dest ? line : old_line;
15023 // [HGM] vari: routines for shelving variations
15026 PushTail(int firstMove, int lastMove)
15028 int i, j, nrMoves = lastMove - firstMove;
15030 if(appData.icsActive) { // only in local mode
15031 forwardMostMove = currentMove; // mimic old ICS behavior
15034 if(storedGames >= MAX_VARIATIONS-1) return;
15036 // push current tail of game on stack
15037 savedResult[storedGames] = gameInfo.result;
15038 savedDetails[storedGames] = gameInfo.resultDetails;
15039 gameInfo.resultDetails = NULL;
15040 savedFirst[storedGames] = firstMove;
15041 savedLast [storedGames] = lastMove;
15042 savedFramePtr[storedGames] = framePtr;
15043 framePtr -= nrMoves; // reserve space for the boards
15044 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15045 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15046 for(j=0; j<MOVE_LEN; j++)
15047 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15048 for(j=0; j<2*MOVE_LEN; j++)
15049 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15050 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15051 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15052 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15053 pvInfoList[firstMove+i-1].depth = 0;
15054 commentList[framePtr+i] = commentList[firstMove+i];
15055 commentList[firstMove+i] = NULL;
15059 forwardMostMove = firstMove; // truncate game so we can start variation
15060 if(storedGames == 1) GreyRevert(FALSE);
15064 PopTail(Boolean annotate)
15067 char buf[8000], moveBuf[20];
15069 if(appData.icsActive) return FALSE; // only in local mode
15070 if(!storedGames) return FALSE; // sanity
15071 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15074 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15075 nrMoves = savedLast[storedGames] - currentMove;
15078 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15079 else strcpy(buf, "(");
15080 for(i=currentMove; i<forwardMostMove; i++) {
15082 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15083 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15084 strcat(buf, moveBuf);
15085 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15086 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15090 for(i=1; i<=nrMoves; i++) { // copy last variation back
15091 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15092 for(j=0; j<MOVE_LEN; j++)
15093 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15094 for(j=0; j<2*MOVE_LEN; j++)
15095 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15096 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15097 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15098 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15099 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15100 commentList[currentMove+i] = commentList[framePtr+i];
15101 commentList[framePtr+i] = NULL;
15103 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15104 framePtr = savedFramePtr[storedGames];
15105 gameInfo.result = savedResult[storedGames];
15106 if(gameInfo.resultDetails != NULL) {
15107 free(gameInfo.resultDetails);
15109 gameInfo.resultDetails = savedDetails[storedGames];
15110 forwardMostMove = currentMove + nrMoves;
15111 if(storedGames == 0) GreyRevert(TRUE);
15117 { // remove all shelved variations
15119 for(i=0; i<storedGames; i++) {
15120 if(savedDetails[i])
15121 free(savedDetails[i]);
15122 savedDetails[i] = NULL;
15124 for(i=framePtr; i<MAX_MOVES; i++) {
15125 if(commentList[i]) free(commentList[i]);
15126 commentList[i] = NULL;
15128 framePtr = MAX_MOVES-1;
15133 LoadVariation(int index, char *text)
15134 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15135 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15136 int level = 0, move;
15138 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15139 // first find outermost bracketing variation
15140 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15141 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15142 if(*p == '{') wait = '}'; else
15143 if(*p == '[') wait = ']'; else
15144 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15145 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15147 if(*p == wait) wait = NULLCHAR; // closing ]} found
15150 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15151 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15152 end[1] = NULLCHAR; // clip off comment beyond variation
15153 ToNrEvent(currentMove-1);
15154 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15155 // kludge: use ParsePV() to append variation to game
15156 move = currentMove;
15157 ParsePV(start, TRUE);
15158 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15159 ClearPremoveHighlights();
15161 ToNrEvent(currentMove+1);