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 */
247 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
259 /* States for ics_getting_history */
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
267 /* whosays values for GameEnds */
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
279 /* Different types of move when calling RegisterMove */
281 #define CMAIL_RESIGN 1
283 #define CMAIL_ACCEPT 3
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
290 /* Telnet protocol constants */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
303 assert( dst != NULL );
304 assert( src != NULL );
307 strncpy( dst, src, count );
308 dst[ count-1 ] = '\0';
312 /* Some compiler can't cast u64 to double
313 * This function do the job for us:
315 * We use the highest bit for cast, this only
316 * works if the highest bit is not
317 * in use (This should not happen)
319 * We used this for all compiler
322 u64ToDouble(u64 value)
325 u64 tmp = value & u64Const(0x7fffffffffffffff);
326 r = (double)(s64)tmp;
327 if (value & u64Const(0x8000000000000000))
328 r += 9.2233720368547758080e18; /* 2^63 */
332 /* Fake up flags for now, as we aren't keeping track of castling
333 availability yet. [HGM] Change of logic: the flag now only
334 indicates the type of castlings allowed by the rule of the game.
335 The actual rights themselves are maintained in the array
336 castlingRights, as part of the game history, and are not probed
342 int flags = F_ALL_CASTLE_OK;
343 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344 switch (gameInfo.variant) {
346 flags &= ~F_ALL_CASTLE_OK;
347 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348 flags |= F_IGNORE_CHECK;
350 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
353 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
355 case VariantKriegspiel:
356 flags |= F_KRIEGSPIEL_CAPTURE;
358 case VariantCapaRandom:
359 case VariantFischeRandom:
360 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361 case VariantNoCastle:
362 case VariantShatranj:
365 flags &= ~F_ALL_CASTLE_OK;
373 FILE *gameFileFP, *debugFP;
376 [AS] Note: sometimes, the sscanf() function is used to parse the input
377 into a fixed-size buffer. Because of this, we must be prepared to
378 receive strings as long as the size of the input buffer, which is currently
379 set to 4K for Windows and 8K for the rest.
380 So, we must either allocate sufficiently large buffers here, or
381 reduce the size of the input buffer in the input reading part.
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
388 ChessProgramState first, second;
390 /* premove variables */
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
440 /* animateTraining preserves the state of appData.animate
441 * when Training mode is activated. This allows the
442 * response to be animated when appData.animate == TRUE and
443 * appData.animateDragging == TRUE.
445 Boolean animateTraining;
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char initialRights[BOARD_FILES];
455 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int initialRulePlies, FENrulePlies;
457 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
460 int mute; // mute all sounds
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
476 ChessSquare FIDEArray[2][BOARD_FILES] = {
477 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480 BlackKing, BlackBishop, BlackKnight, BlackRook }
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487 BlackKing, BlackKing, BlackKnight, BlackRook }
490 ChessSquare KnightmateArray[2][BOARD_FILES] = {
491 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493 { BlackRook, BlackMan, BlackBishop, BlackQueen,
494 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackMan, BlackFerz,
515 BlackKing, BlackMan, BlackKnight, BlackRook }
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
538 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
543 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
545 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
550 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
552 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
558 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
560 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
563 #define GothicArray CapablancaArray
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
569 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
571 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
574 #define FalconArray CapablancaArray
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
596 Board initialPosition;
599 /* Convert str to a rating. Checks for special cases of "----",
601 "++++", etc. Also strips ()'s */
603 string_to_rating(str)
606 while(*str && !isdigit(*str)) ++str;
608 return 0; /* One of the special "no rating" cases */
616 /* Init programStats */
617 programStats.movelist[0] = 0;
618 programStats.depth = 0;
619 programStats.nr_moves = 0;
620 programStats.moves_left = 0;
621 programStats.nodes = 0;
622 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
623 programStats.score = 0;
624 programStats.got_only_move = 0;
625 programStats.got_fail = 0;
626 programStats.line_is_book = 0;
632 int matched, min, sec;
634 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
636 GetTimeMark(&programStartTime);
637 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
640 programStats.ok_to_send = 1;
641 programStats.seen_stat = 0;
644 * Initialize game list
650 * Internet chess server status
652 if (appData.icsActive) {
653 appData.matchMode = FALSE;
654 appData.matchGames = 0;
656 appData.noChessProgram = !appData.zippyPlay;
658 appData.zippyPlay = FALSE;
659 appData.zippyTalk = FALSE;
660 appData.noChessProgram = TRUE;
662 if (*appData.icsHelper != NULLCHAR) {
663 appData.useTelnet = TRUE;
664 appData.telnetProgram = appData.icsHelper;
667 appData.zippyTalk = appData.zippyPlay = FALSE;
670 /* [AS] Initialize pv info list [HGM] and game state */
674 for( i=0; i<=framePtr; i++ ) {
675 pvInfoList[i].depth = -1;
676 boards[i][EP_STATUS] = EP_NONE;
677 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
682 * Parse timeControl resource
684 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685 appData.movesPerSession)) {
687 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688 DisplayFatalError(buf, 0, 2);
692 * Parse searchTime resource
694 if (*appData.searchTime != NULLCHAR) {
695 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
697 searchTime = min * 60;
698 } else if (matched == 2) {
699 searchTime = min * 60 + sec;
702 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703 DisplayFatalError(buf, 0, 2);
707 /* [AS] Adjudication threshold */
708 adjudicateLossThreshold = appData.adjudicateLossThreshold;
710 first.which = "first";
711 second.which = "second";
712 first.maybeThinking = second.maybeThinking = FALSE;
713 first.pr = second.pr = NoProc;
714 first.isr = second.isr = NULL;
715 first.sendTime = second.sendTime = 2;
716 first.sendDrawOffers = 1;
717 if (appData.firstPlaysBlack) {
718 first.twoMachinesColor = "black\n";
719 second.twoMachinesColor = "white\n";
721 first.twoMachinesColor = "white\n";
722 second.twoMachinesColor = "black\n";
724 first.program = appData.firstChessProgram;
725 second.program = appData.secondChessProgram;
726 first.host = appData.firstHost;
727 second.host = appData.secondHost;
728 first.dir = appData.firstDirectory;
729 second.dir = appData.secondDirectory;
730 first.other = &second;
731 second.other = &first;
732 first.initString = appData.initString;
733 second.initString = appData.secondInitString;
734 first.computerString = appData.firstComputerString;
735 second.computerString = appData.secondComputerString;
736 first.useSigint = second.useSigint = TRUE;
737 first.useSigterm = second.useSigterm = TRUE;
738 first.reuse = appData.reuseFirst;
739 second.reuse = appData.reuseSecond;
740 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
741 second.nps = appData.secondNPS;
742 first.useSetboard = second.useSetboard = FALSE;
743 first.useSAN = second.useSAN = FALSE;
744 first.usePing = second.usePing = FALSE;
745 first.lastPing = second.lastPing = 0;
746 first.lastPong = second.lastPong = 0;
747 first.usePlayother = second.usePlayother = FALSE;
748 first.useColors = second.useColors = TRUE;
749 first.useUsermove = second.useUsermove = FALSE;
750 first.sendICS = second.sendICS = FALSE;
751 first.sendName = second.sendName = appData.icsActive;
752 first.sdKludge = second.sdKludge = FALSE;
753 first.stKludge = second.stKludge = FALSE;
754 TidyProgramName(first.program, first.host, first.tidy);
755 TidyProgramName(second.program, second.host, second.tidy);
756 first.matchWins = second.matchWins = 0;
757 strcpy(first.variants, appData.variant);
758 strcpy(second.variants, appData.variant);
759 first.analysisSupport = second.analysisSupport = 2; /* detect */
760 first.analyzing = second.analyzing = FALSE;
761 first.initDone = second.initDone = FALSE;
763 /* New features added by Tord: */
764 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766 /* End of new features added by Tord. */
767 first.fenOverride = appData.fenOverride1;
768 second.fenOverride = appData.fenOverride2;
770 /* [HGM] time odds: set factor for each machine */
771 first.timeOdds = appData.firstTimeOdds;
772 second.timeOdds = appData.secondTimeOdds;
774 if(appData.timeOddsMode) {
775 norm = first.timeOdds;
776 if(norm > second.timeOdds) norm = second.timeOdds;
778 first.timeOdds /= norm;
779 second.timeOdds /= norm;
782 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783 first.accumulateTC = appData.firstAccumulateTC;
784 second.accumulateTC = appData.secondAccumulateTC;
785 first.maxNrOfSessions = second.maxNrOfSessions = 1;
788 first.debug = second.debug = FALSE;
789 first.supportsNPS = second.supportsNPS = UNKNOWN;
792 first.optionSettings = appData.firstOptions;
793 second.optionSettings = appData.secondOptions;
795 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797 first.isUCI = appData.firstIsUCI; /* [AS] */
798 second.isUCI = appData.secondIsUCI; /* [AS] */
799 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
802 if (appData.firstProtocolVersion > PROTOVER ||
803 appData.firstProtocolVersion < 1) {
805 sprintf(buf, _("protocol version %d not supported"),
806 appData.firstProtocolVersion);
807 DisplayFatalError(buf, 0, 2);
809 first.protocolVersion = appData.firstProtocolVersion;
812 if (appData.secondProtocolVersion > PROTOVER ||
813 appData.secondProtocolVersion < 1) {
815 sprintf(buf, _("protocol version %d not supported"),
816 appData.secondProtocolVersion);
817 DisplayFatalError(buf, 0, 2);
819 second.protocolVersion = appData.secondProtocolVersion;
822 if (appData.icsActive) {
823 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
824 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826 appData.clockMode = FALSE;
827 first.sendTime = second.sendTime = 0;
831 /* Override some settings from environment variables, for backward
832 compatibility. Unfortunately it's not feasible to have the env
833 vars just set defaults, at least in xboard. Ugh.
835 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
840 if (appData.noChessProgram) {
841 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842 sprintf(programVersion, "%s", PACKAGE_STRING);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849 if (!appData.icsActive) {
851 /* Check for variants that are supported only in ICS mode,
852 or not at all. Some that are accepted here nevertheless
853 have bugs; see comments below.
855 VariantClass variant = StringToVariant(appData.variant);
857 case VariantBughouse: /* need four players and two boards */
858 case VariantKriegspiel: /* need to hide pieces and move details */
859 /* case VariantFischeRandom: (Fabien: moved below) */
860 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861 DisplayFatalError(buf, 0, 2);
865 case VariantLoadable:
875 sprintf(buf, _("Unknown variant name %s"), appData.variant);
876 DisplayFatalError(buf, 0, 2);
879 case VariantXiangqi: /* [HGM] repetition rules not implemented */
880 case VariantFairy: /* [HGM] TestLegality definitely off! */
881 case VariantGothic: /* [HGM] should work */
882 case VariantCapablanca: /* [HGM] should work */
883 case VariantCourier: /* [HGM] initial forced moves not implemented */
884 case VariantShogi: /* [HGM] drops not tested for legality */
885 case VariantKnightmate: /* [HGM] should work */
886 case VariantCylinder: /* [HGM] untested */
887 case VariantFalcon: /* [HGM] untested */
888 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889 offboard interposition not understood */
890 case VariantNormal: /* definitely works! */
891 case VariantWildCastle: /* pieces not automatically shuffled */
892 case VariantNoCastle: /* pieces not automatically shuffled */
893 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894 case VariantLosers: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantSuicide: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantGiveaway: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantTwoKings: /* should work */
901 case VariantAtomic: /* should work except for win condition */
902 case Variant3Check: /* should work except for win condition */
903 case VariantShatranj: /* should work except for all win conditions */
904 case VariantMakruk: /* should work except for daw countdown */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1017 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1020 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021 else sprintf(buf, "+%s+%d", tc, ti);
1024 sprintf(buf, "+%d/%s", mps, tc);
1025 else sprintf(buf, "+%s", tc);
1027 fullTimeControlString = StrSave(buf);
1029 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1034 /* Parse second time control */
1037 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1045 timeControl_2 = tc2 * 1000;
1055 timeControl = tc1 * 1000;
1058 timeIncrement = ti * 1000; /* convert to ms */
1059 movesPerSession = 0;
1062 movesPerSession = mps;
1070 if (appData.debugMode) {
1071 fprintf(debugFP, "%s\n", programVersion);
1074 set_cont_sequence(appData.wrapContSeq);
1075 if (appData.matchGames > 0) {
1076 appData.matchMode = TRUE;
1077 } else if (appData.matchMode) {
1078 appData.matchGames = 1;
1080 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081 appData.matchGames = appData.sameColorGames;
1082 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1087 if (appData.noChessProgram || first.protocolVersion == 1) {
1090 /* kludge: allow timeout for initial "feature" commands */
1092 DisplayMessage("", _("Starting chess program"));
1093 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1098 InitBackEnd3 P((void))
1100 GameMode initialMode;
1104 InitChessProgram(&first, startedFromSetupPosition);
1107 if (appData.icsActive) {
1109 /* [DM] Make a console window if needed [HGM] merged ifs */
1114 if (*appData.icsCommPort != NULLCHAR) {
1115 sprintf(buf, _("Could not open comm port %s"),
1116 appData.icsCommPort);
1118 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1119 appData.icsHost, appData.icsPort);
1121 DisplayFatalError(buf, err, 1);
1126 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1128 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131 } else if (appData.noChessProgram) {
1137 if (*appData.cmailGameName != NULLCHAR) {
1139 OpenLoopback(&cmailPR);
1141 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1145 DisplayMessage("", "");
1146 if (StrCaseCmp(appData.initialMode, "") == 0) {
1147 initialMode = BeginningOfGame;
1148 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149 initialMode = TwoMachinesPlay;
1150 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151 initialMode = AnalyzeFile;
1152 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153 initialMode = AnalyzeMode;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155 initialMode = MachinePlaysWhite;
1156 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157 initialMode = MachinePlaysBlack;
1158 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159 initialMode = EditGame;
1160 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161 initialMode = EditPosition;
1162 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163 initialMode = Training;
1165 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166 DisplayFatalError(buf, 0, 2);
1170 if (appData.matchMode) {
1171 /* Set up machine vs. machine match */
1172 if (appData.noChessProgram) {
1173 DisplayFatalError(_("Can't have a match with no chess programs"),
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 int index = appData.loadGameIndex; // [HGM] autoinc
1181 if(index<0) lastIndex = index = 1;
1182 if (!LoadGameFromFile(appData.loadGameFile,
1184 appData.loadGameFile, FALSE)) {
1185 DisplayFatalError(_("Bad game file"), 0, 1);
1188 } else if (*appData.loadPositionFile != NULLCHAR) {
1189 int index = appData.loadPositionIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadPositionFromFile(appData.loadPositionFile,
1193 appData.loadPositionFile)) {
1194 DisplayFatalError(_("Bad position file"), 0, 1);
1199 } else if (*appData.cmailGameName != NULLCHAR) {
1200 /* Set up cmail mode */
1201 ReloadCmailMsgEvent(TRUE);
1203 /* Set up other modes */
1204 if (initialMode == AnalyzeFile) {
1205 if (*appData.loadGameFile == NULLCHAR) {
1206 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1210 if (*appData.loadGameFile != NULLCHAR) {
1211 (void) LoadGameFromFile(appData.loadGameFile,
1212 appData.loadGameIndex,
1213 appData.loadGameFile, TRUE);
1214 } else if (*appData.loadPositionFile != NULLCHAR) {
1215 (void) LoadPositionFromFile(appData.loadPositionFile,
1216 appData.loadPositionIndex,
1217 appData.loadPositionFile);
1218 /* [HGM] try to make self-starting even after FEN load */
1219 /* to allow automatic setup of fairy variants with wtm */
1220 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221 gameMode = BeginningOfGame;
1222 setboardSpoiledMachineBlack = 1;
1224 /* [HGM] loadPos: make that every new game uses the setup */
1225 /* from file as long as we do not switch variant */
1226 if(!blackPlaysFirst) {
1227 startedFromPositionFile = TRUE;
1228 CopyBoard(filePosition, boards[0]);
1231 if (initialMode == AnalyzeMode) {
1232 if (appData.noChessProgram) {
1233 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1241 } else if (initialMode == AnalyzeFile) {
1242 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243 ShowThinkingEvent();
1245 AnalysisPeriodicEvent(1);
1246 } else if (initialMode == MachinePlaysWhite) {
1247 if (appData.noChessProgram) {
1248 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1252 if (appData.icsActive) {
1253 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1257 MachineWhiteEvent();
1258 } else if (initialMode == MachinePlaysBlack) {
1259 if (appData.noChessProgram) {
1260 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1264 if (appData.icsActive) {
1265 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1269 MachineBlackEvent();
1270 } else if (initialMode == TwoMachinesPlay) {
1271 if (appData.noChessProgram) {
1272 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1276 if (appData.icsActive) {
1277 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1282 } else if (initialMode == EditGame) {
1284 } else if (initialMode == EditPosition) {
1285 EditPositionEvent();
1286 } else if (initialMode == Training) {
1287 if (*appData.loadGameFile == NULLCHAR) {
1288 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1297 * Establish will establish a contact to a remote host.port.
1298 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299 * used to talk to the host.
1300 * Returns 0 if okay, error code if not.
1307 if (*appData.icsCommPort != NULLCHAR) {
1308 /* Talk to the host through a serial comm port */
1309 return OpenCommPort(appData.icsCommPort, &icsPR);
1311 } else if (*appData.gateway != NULLCHAR) {
1312 if (*appData.remoteShell == NULLCHAR) {
1313 /* Use the rcmd protocol to run telnet program on a gateway host */
1314 snprintf(buf, sizeof(buf), "%s %s %s",
1315 appData.telnetProgram, appData.icsHost, appData.icsPort);
1316 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1319 /* Use the rsh program to run telnet program on a gateway host */
1320 if (*appData.remoteUser == NULLCHAR) {
1321 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322 appData.gateway, appData.telnetProgram,
1323 appData.icsHost, appData.icsPort);
1325 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326 appData.remoteShell, appData.gateway,
1327 appData.remoteUser, appData.telnetProgram,
1328 appData.icsHost, appData.icsPort);
1330 return StartChildProcess(buf, "", &icsPR);
1333 } else if (appData.useTelnet) {
1334 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1337 /* TCP socket interface differs somewhat between
1338 Unix and NT; handle details in the front end.
1340 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1345 show_bytes(fp, buf, count)
1351 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352 fprintf(fp, "\\%03o", *buf & 0xff);
1361 /* Returns an errno value */
1363 OutputMaybeTelnet(pr, message, count, outError)
1369 char buf[8192], *p, *q, *buflim;
1370 int left, newcount, outcount;
1372 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373 *appData.gateway != NULLCHAR) {
1374 if (appData.debugMode) {
1375 fprintf(debugFP, ">ICS: ");
1376 show_bytes(debugFP, message, count);
1377 fprintf(debugFP, "\n");
1379 return OutputToProcess(pr, message, count, outError);
1382 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1389 if (appData.debugMode) {
1390 fprintf(debugFP, ">ICS: ");
1391 show_bytes(debugFP, buf, newcount);
1392 fprintf(debugFP, "\n");
1394 outcount = OutputToProcess(pr, buf, newcount, outError);
1395 if (outcount < newcount) return -1; /* to be sure */
1402 } else if (((unsigned char) *p) == TN_IAC) {
1403 *q++ = (char) TN_IAC;
1410 if (appData.debugMode) {
1411 fprintf(debugFP, ">ICS: ");
1412 show_bytes(debugFP, buf, newcount);
1413 fprintf(debugFP, "\n");
1415 outcount = OutputToProcess(pr, buf, newcount, outError);
1416 if (outcount < newcount) return -1; /* to be sure */
1421 read_from_player(isr, closure, message, count, error)
1428 int outError, outCount;
1429 static int gotEof = 0;
1431 /* Pass data read from player on to ICS */
1434 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435 if (outCount < count) {
1436 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1438 } else if (count < 0) {
1439 RemoveInputSource(isr);
1440 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441 } else if (gotEof++ > 0) {
1442 RemoveInputSource(isr);
1443 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1449 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452 SendToICS("date\n");
1453 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1459 char buffer[MSG_SIZ];
1462 va_start(args, format);
1463 vsnprintf(buffer, sizeof(buffer), format, args);
1464 buffer[sizeof(buffer)-1] = '\0';
1473 int count, outCount, outError;
1475 if (icsPR == NULL) return;
1478 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479 if (outCount < count) {
1480 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1484 /* This is used for sending logon scripts to the ICS. Sending
1485 without a delay causes problems when using timestamp on ICC
1486 (at least on my machine). */
1488 SendToICSDelayed(s,msdelay)
1492 int count, outCount, outError;
1494 if (icsPR == NULL) return;
1497 if (appData.debugMode) {
1498 fprintf(debugFP, ">ICS: ");
1499 show_bytes(debugFP, s, count);
1500 fprintf(debugFP, "\n");
1502 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1504 if (outCount < count) {
1505 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1510 /* Remove all highlighting escape sequences in s
1511 Also deletes any suffix starting with '('
1514 StripHighlightAndTitle(s)
1517 static char retbuf[MSG_SIZ];
1520 while (*s != NULLCHAR) {
1521 while (*s == '\033') {
1522 while (*s != NULLCHAR && !isalpha(*s)) s++;
1523 if (*s != NULLCHAR) s++;
1525 while (*s != NULLCHAR && *s != '\033') {
1526 if (*s == '(' || *s == '[') {
1537 /* Remove all highlighting escape sequences in s */
1542 static char retbuf[MSG_SIZ];
1545 while (*s != NULLCHAR) {
1546 while (*s == '\033') {
1547 while (*s != NULLCHAR && !isalpha(*s)) s++;
1548 if (*s != NULLCHAR) s++;
1550 while (*s != NULLCHAR && *s != '\033') {
1558 char *variantNames[] = VARIANT_NAMES;
1563 return variantNames[v];
1567 /* Identify a variant from the strings the chess servers use or the
1568 PGN Variant tag names we use. */
1575 VariantClass v = VariantNormal;
1576 int i, found = FALSE;
1581 /* [HGM] skip over optional board-size prefixes */
1582 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584 while( *e++ != '_');
1587 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1591 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592 if (StrCaseStr(e, variantNames[i])) {
1593 v = (VariantClass) i;
1600 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601 || StrCaseStr(e, "wild/fr")
1602 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603 v = VariantFischeRandom;
1604 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605 (i = 1, p = StrCaseStr(e, "w"))) {
1607 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1614 case 0: /* FICS only, actually */
1616 /* Castling legal even if K starts on d-file */
1617 v = VariantWildCastle;
1622 /* Castling illegal even if K & R happen to start in
1623 normal positions. */
1624 v = VariantNoCastle;
1637 /* Castling legal iff K & R start in normal positions */
1643 /* Special wilds for position setup; unclear what to do here */
1644 v = VariantLoadable;
1647 /* Bizarre ICC game */
1648 v = VariantTwoKings;
1651 v = VariantKriegspiel;
1657 v = VariantFischeRandom;
1660 v = VariantCrazyhouse;
1663 v = VariantBughouse;
1669 /* Not quite the same as FICS suicide! */
1670 v = VariantGiveaway;
1676 v = VariantShatranj;
1679 /* Temporary names for future ICC types. The name *will* change in
1680 the next xboard/WinBoard release after ICC defines it. */
1718 v = VariantCapablanca;
1721 v = VariantKnightmate;
1727 v = VariantCylinder;
1733 v = VariantCapaRandom;
1736 v = VariantBerolina;
1748 /* Found "wild" or "w" in the string but no number;
1749 must assume it's normal chess. */
1753 sprintf(buf, _("Unknown wild type %d"), wnum);
1754 DisplayError(buf, 0);
1760 if (appData.debugMode) {
1761 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762 e, wnum, VariantName(v));
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771 advance *index beyond it, and set leftover_start to the new value of
1772 *index; else return FALSE. If pattern contains the character '*', it
1773 matches any sequence of characters not containing '\r', '\n', or the
1774 character following the '*' (if any), and the matched sequence(s) are
1775 copied into star_match.
1778 looking_at(buf, index, pattern)
1783 char *bufp = &buf[*index], *patternp = pattern;
1785 char *matchp = star_match[0];
1788 if (*patternp == NULLCHAR) {
1789 *index = leftover_start = bufp - buf;
1793 if (*bufp == NULLCHAR) return FALSE;
1794 if (*patternp == '*') {
1795 if (*bufp == *(patternp + 1)) {
1797 matchp = star_match[++star_count];
1801 } else if (*bufp == '\n' || *bufp == '\r') {
1803 if (*patternp == NULLCHAR)
1808 *matchp++ = *bufp++;
1812 if (*patternp != *bufp) return FALSE;
1819 SendToPlayer(data, length)
1823 int error, outCount;
1824 outCount = OutputToProcess(NoProc, data, length, &error);
1825 if (outCount < length) {
1826 DisplayFatalError(_("Error writing to display"), error, 1);
1831 PackHolding(packed, holding)
1843 switch (runlength) {
1854 sprintf(q, "%d", runlength);
1866 /* Telnet protocol requests from the front end */
1868 TelnetRequest(ddww, option)
1869 unsigned char ddww, option;
1871 unsigned char msg[3];
1872 int outCount, outError;
1874 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1876 if (appData.debugMode) {
1877 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1893 sprintf(buf1, "%d", ddww);
1902 sprintf(buf2, "%d", option);
1905 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1910 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1912 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919 if (!appData.icsActive) return;
1920 TelnetRequest(TN_DO, TN_ECHO);
1926 if (!appData.icsActive) return;
1927 TelnetRequest(TN_DONT, TN_ECHO);
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1933 /* put the holdings sent to us by the server on the board holdings area */
1934 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1938 if(gameInfo.holdingsWidth < 2) return;
1939 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940 return; // prevent overwriting by pre-board holdings
1942 if( (int)lowestPiece >= BlackPawn ) {
1945 holdingsStartRow = BOARD_HEIGHT-1;
1948 holdingsColumn = BOARD_WIDTH-1;
1949 countsColumn = BOARD_WIDTH-2;
1950 holdingsStartRow = 0;
1954 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955 board[i][holdingsColumn] = EmptySquare;
1956 board[i][countsColumn] = (ChessSquare) 0;
1958 while( (p=*holdings++) != NULLCHAR ) {
1959 piece = CharToPiece( ToUpper(p) );
1960 if(piece == EmptySquare) continue;
1961 /*j = (int) piece - (int) WhitePawn;*/
1962 j = PieceToNumber(piece);
1963 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964 if(j < 0) continue; /* should not happen */
1965 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967 board[holdingsStartRow+j*direction][countsColumn]++;
1973 VariantSwitch(Board board, VariantClass newVariant)
1975 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1978 startedFromPositionFile = FALSE;
1979 if(gameInfo.variant == newVariant) return;
1981 /* [HGM] This routine is called each time an assignment is made to
1982 * gameInfo.variant during a game, to make sure the board sizes
1983 * are set to match the new variant. If that means adding or deleting
1984 * holdings, we shift the playing board accordingly
1985 * This kludge is needed because in ICS observe mode, we get boards
1986 * of an ongoing game without knowing the variant, and learn about the
1987 * latter only later. This can be because of the move list we requested,
1988 * in which case the game history is refilled from the beginning anyway,
1989 * but also when receiving holdings of a crazyhouse game. In the latter
1990 * case we want to add those holdings to the already received position.
1994 if (appData.debugMode) {
1995 fprintf(debugFP, "Switch board from %s to %s\n",
1996 VariantName(gameInfo.variant), VariantName(newVariant));
1997 setbuf(debugFP, NULL);
1999 shuffleOpenings = 0; /* [HGM] shuffle */
2000 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2004 newWidth = 9; newHeight = 9;
2005 gameInfo.holdingsSize = 7;
2006 case VariantBughouse:
2007 case VariantCrazyhouse:
2008 newHoldingsWidth = 2; break;
2012 newHoldingsWidth = 2;
2013 gameInfo.holdingsSize = 8;
2016 case VariantCapablanca:
2017 case VariantCapaRandom:
2020 newHoldingsWidth = gameInfo.holdingsSize = 0;
2023 if(newWidth != gameInfo.boardWidth ||
2024 newHeight != gameInfo.boardHeight ||
2025 newHoldingsWidth != gameInfo.holdingsWidth ) {
2027 /* shift position to new playing area, if needed */
2028 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029 for(i=0; i<BOARD_HEIGHT; i++)
2030 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2033 for(i=0; i<newHeight; i++) {
2034 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2037 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038 for(i=0; i<BOARD_HEIGHT; i++)
2039 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2043 gameInfo.boardWidth = newWidth;
2044 gameInfo.boardHeight = newHeight;
2045 gameInfo.holdingsWidth = newHoldingsWidth;
2046 gameInfo.variant = newVariant;
2047 InitDrawingSizes(-2, 0);
2048 } else gameInfo.variant = newVariant;
2049 CopyBoard(oldBoard, board); // remember correctly formatted board
2050 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2051 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2054 static int loggedOn = FALSE;
2056 /*-- Game start info cache: --*/
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\ ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2086 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088 if(r < minRating+100 && r >=0 ) r = minRating+100;
2089 if(r > maxRating) r = maxRating;
2090 if(tc < 1.) tc = 1.;
2091 if(tc > 95.) tc = 95.;
2092 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2093 y = ((double)r - minRating)/(maxRating - minRating)
2094 * (h-vMargin-squareSize/8-1) + vMargin;
2095 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096 if(strstr(seekAdList[i], " u ")) color = 1;
2097 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098 !strstr(seekAdList[i], "bullet") &&
2099 !strstr(seekAdList[i], "blitz") &&
2100 !strstr(seekAdList[i], "standard") ) color = 2;
2101 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2106 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2108 char buf[MSG_SIZ], *ext = "";
2109 VariantClass v = StringToVariant(type);
2110 if(strstr(type, "wild")) {
2111 ext = type + 4; // append wild number
2112 if(v == VariantFischeRandom) type = "chess960"; else
2113 if(v == VariantLoadable) type = "setup"; else
2114 type = VariantName(v);
2116 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122 seekNrList[nrOfSeekAds] = nr;
2123 zList[nrOfSeekAds] = 0;
2124 seekAdList[nrOfSeekAds++] = StrSave(buf);
2125 if(plot) PlotSeekAd(nrOfSeekAds-1);
2132 int x = xList[i], y = yList[i], d=squareSize/4, k;
2133 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135 // now replot every dot that overlapped
2136 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137 int xx = xList[k], yy = yList[k];
2138 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139 DrawSeekDot(xx, yy, colorList[k]);
2144 RemoveSeekAd(int nr)
2147 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2149 if(seekAdList[i]) free(seekAdList[i]);
2150 seekAdList[i] = seekAdList[--nrOfSeekAds];
2151 seekNrList[i] = seekNrList[nrOfSeekAds];
2152 ratingList[i] = ratingList[nrOfSeekAds];
2153 colorList[i] = colorList[nrOfSeekAds];
2154 tcList[i] = tcList[nrOfSeekAds];
2155 xList[i] = xList[nrOfSeekAds];
2156 yList[i] = yList[nrOfSeekAds];
2157 zList[i] = zList[nrOfSeekAds];
2158 seekAdList[nrOfSeekAds] = NULL;
2164 MatchSoughtLine(char *line)
2166 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167 int nr, base, inc, u=0; char dummy;
2169 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2172 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2174 // match: compact and save the line
2175 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2184 if(!seekGraphUp) return FALSE;
2186 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2189 DrawSeekBackground(0, 0, w, h);
2190 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2195 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2198 sprintf(buf, "%d", i);
2199 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2202 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203 for(i=1; i<100; i+=(i<10?1:5)) {
2204 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2205 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2208 sprintf(buf, "%d", i);
2209 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2212 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2218 static int lastDown = 0, displayed = 0, lastSecond;
2219 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220 if(click == Release || moving) return FALSE;
2222 soughtPending = TRUE;
2223 SendToICS(ics_prefix);
2224 SendToICS("sought\n"); // should this be "sought all"?
2225 } else { // issue challenge based on clicked ad
2226 int dist = 10000; int i, closest = 0, second = 0;
2227 for(i=0; i<nrOfSeekAds; i++) {
2228 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2229 if(d < dist) { dist = d; closest = i; }
2230 second += (d - zList[i] < 120); // count in-range ads
2231 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2235 second = (second > 1);
2236 if(displayed != closest || second != lastSecond) {
2237 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238 lastSecond = second; displayed = closest;
2240 if(click == Press) {
2241 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2244 } // on press 'hit', only show info
2245 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246 sprintf(buf, "play %d\n", seekNrList[closest]);
2247 SendToICS(ics_prefix);
2249 return TRUE; // let incoming board of started game pop down the graph
2250 } else if(click == Release) { // release 'miss' is ignored
2251 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252 if(moving == 2) { // right up-click
2253 nrOfSeekAds = 0; // refresh graph
2254 soughtPending = TRUE;
2255 SendToICS(ics_prefix);
2256 SendToICS("sought\n"); // should this be "sought all"?
2259 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260 // press miss or release hit 'pop down' seek graph
2261 seekGraphUp = FALSE;
2262 DrawPosition(TRUE, NULL);
2268 read_from_ics(isr, closure, data, count, error)
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2285 static int started = STARTED_NONE;
2286 static char parse[20000];
2287 static int parse_pos = 0;
2288 static char buf[BUF_SIZE + 1];
2289 static int firstTime = TRUE, intfSet = FALSE;
2290 static ColorClass prevColor = ColorNormal;
2291 static int savingComment = FALSE;
2292 static int cmatch = 0; // continuation sequence match
2299 int backup; /* [DM] For zippy color lines */
2301 char talker[MSG_SIZ]; // [HGM] chat
2304 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2306 if (appData.debugMode) {
2308 fprintf(debugFP, "<ICS: ");
2309 show_bytes(debugFP, data, count);
2310 fprintf(debugFP, "\n");
2314 if (appData.debugMode) { int f = forwardMostMove;
2315 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2320 /* If last read ended with a partial line that we couldn't parse,
2321 prepend it to the new read and try again. */
2322 if (leftover_len > 0) {
2323 for (i=0; i<leftover_len; i++)
2324 buf[i] = buf[leftover_start + i];
2327 /* copy new characters into the buffer */
2328 bp = buf + leftover_len;
2329 buf_len=leftover_len;
2330 for (i=0; i<count; i++)
2333 if (data[i] == '\r')
2336 // join lines split by ICS?
2337 if (!appData.noJoin)
2340 Joining just consists of finding matches against the
2341 continuation sequence, and discarding that sequence
2342 if found instead of copying it. So, until a match
2343 fails, there's nothing to do since it might be the
2344 complete sequence, and thus, something we don't want
2347 if (data[i] == cont_seq[cmatch])
2350 if (cmatch == strlen(cont_seq))
2352 cmatch = 0; // complete match. just reset the counter
2355 it's possible for the ICS to not include the space
2356 at the end of the last word, making our [correct]
2357 join operation fuse two separate words. the server
2358 does this when the space occurs at the width setting.
2360 if (!buf_len || buf[buf_len-1] != ' ')
2371 match failed, so we have to copy what matched before
2372 falling through and copying this character. In reality,
2373 this will only ever be just the newline character, but
2374 it doesn't hurt to be precise.
2376 strncpy(bp, cont_seq, cmatch);
2388 buf[buf_len] = NULLCHAR;
2389 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2394 while (i < buf_len) {
2395 /* Deal with part of the TELNET option negotiation
2396 protocol. We refuse to do anything beyond the
2397 defaults, except that we allow the WILL ECHO option,
2398 which ICS uses to turn off password echoing when we are
2399 directly connected to it. We reject this option
2400 if localLineEditing mode is on (always on in xboard)
2401 and we are talking to port 23, which might be a real
2402 telnet server that will try to keep WILL ECHO on permanently.
2404 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406 unsigned char option;
2408 switch ((unsigned char) buf[++i]) {
2410 if (appData.debugMode)
2411 fprintf(debugFP, "\n<WILL ");
2412 switch (option = (unsigned char) buf[++i]) {
2414 if (appData.debugMode)
2415 fprintf(debugFP, "ECHO ");
2416 /* Reply only if this is a change, according
2417 to the protocol rules. */
2418 if (remoteEchoOption) break;
2419 if (appData.localLineEditing &&
2420 atoi(appData.icsPort) == TN_PORT) {
2421 TelnetRequest(TN_DONT, TN_ECHO);
2424 TelnetRequest(TN_DO, TN_ECHO);
2425 remoteEchoOption = TRUE;
2429 if (appData.debugMode)
2430 fprintf(debugFP, "%d ", option);
2431 /* Whatever this is, we don't want it. */
2432 TelnetRequest(TN_DONT, option);
2437 if (appData.debugMode)
2438 fprintf(debugFP, "\n<WONT ");
2439 switch (option = (unsigned char) buf[++i]) {
2441 if (appData.debugMode)
2442 fprintf(debugFP, "ECHO ");
2443 /* Reply only if this is a change, according
2444 to the protocol rules. */
2445 if (!remoteEchoOption) break;
2447 TelnetRequest(TN_DONT, TN_ECHO);
2448 remoteEchoOption = FALSE;
2451 if (appData.debugMode)
2452 fprintf(debugFP, "%d ", (unsigned char) option);
2453 /* Whatever this is, it must already be turned
2454 off, because we never agree to turn on
2455 anything non-default, so according to the
2456 protocol rules, we don't reply. */
2461 if (appData.debugMode)
2462 fprintf(debugFP, "\n<DO ");
2463 switch (option = (unsigned char) buf[++i]) {
2465 /* Whatever this is, we refuse to do it. */
2466 if (appData.debugMode)
2467 fprintf(debugFP, "%d ", option);
2468 TelnetRequest(TN_WONT, option);
2473 if (appData.debugMode)
2474 fprintf(debugFP, "\n<DONT ");
2475 switch (option = (unsigned char) buf[++i]) {
2477 if (appData.debugMode)
2478 fprintf(debugFP, "%d ", option);
2479 /* Whatever this is, we are already not doing
2480 it, because we never agree to do anything
2481 non-default, so according to the protocol
2482 rules, we don't reply. */
2487 if (appData.debugMode)
2488 fprintf(debugFP, "\n<IAC ");
2489 /* Doubled IAC; pass it through */
2493 if (appData.debugMode)
2494 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495 /* Drop all other telnet commands on the floor */
2498 if (oldi > next_out)
2499 SendToPlayer(&buf[next_out], oldi - next_out);
2505 /* OK, this at least will *usually* work */
2506 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2510 if (loggedOn && !intfSet) {
2511 if (ics_type == ICS_ICC) {
2513 "/set-quietly interface %s\n/set-quietly style 12\n",
2515 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516 strcat(str, "/set-2 51 1\n/set seek 1\n");
2517 } else if (ics_type == ICS_CHESSNET) {
2518 sprintf(str, "/style 12\n");
2520 strcpy(str, "alias $ @\n$set interface ");
2521 strcat(str, programVersion);
2522 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2526 strcat(str, "$iset nohighlight 1\n");
2528 strcat(str, "$iset lock 1\n$style 12\n");
2531 NotifyFrontendLogin();
2535 if (started == STARTED_COMMENT) {
2536 /* Accumulate characters in comment */
2537 parse[parse_pos++] = buf[i];
2538 if (buf[i] == '\n') {
2539 parse[parse_pos] = NULLCHAR;
2540 if(chattingPartner>=0) {
2542 sprintf(mess, "%s%s", talker, parse);
2543 OutputChatMessage(chattingPartner, mess);
2544 chattingPartner = -1;
2546 if(!suppressKibitz) // [HGM] kibitz
2547 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2548 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2549 int nrDigit = 0, nrAlph = 0, j;
2550 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2551 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2552 parse[parse_pos] = NULLCHAR;
2553 // try to be smart: if it does not look like search info, it should go to
2554 // ICS interaction window after all, not to engine-output window.
2555 for(j=0; j<parse_pos; j++) { // count letters and digits
2556 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2557 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2558 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2560 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2561 int depth=0; float score;
2562 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2563 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2564 pvInfoList[forwardMostMove-1].depth = depth;
2565 pvInfoList[forwardMostMove-1].score = 100*score;
2567 OutputKibitz(suppressKibitz, parse);
2568 next_out = i+1; // [HGM] suppress printing in ICS window
2571 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572 SendToPlayer(tmp, strlen(tmp));
2575 started = STARTED_NONE;
2577 /* Don't match patterns against characters in comment */
2582 if (started == STARTED_CHATTER) {
2583 if (buf[i] != '\n') {
2584 /* Don't match patterns against characters in chatter */
2588 started = STARTED_NONE;
2591 /* Kludge to deal with rcmd protocol */
2592 if (firstTime && looking_at(buf, &i, "\001*")) {
2593 DisplayFatalError(&buf[1], 0, 1);
2599 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602 if (appData.debugMode)
2603 fprintf(debugFP, "ics_type %d\n", ics_type);
2606 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2607 ics_type = ICS_FICS;
2609 if (appData.debugMode)
2610 fprintf(debugFP, "ics_type %d\n", ics_type);
2613 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2614 ics_type = ICS_CHESSNET;
2616 if (appData.debugMode)
2617 fprintf(debugFP, "ics_type %d\n", ics_type);
2622 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2623 looking_at(buf, &i, "Logging you in as \"*\"") ||
2624 looking_at(buf, &i, "will be \"*\""))) {
2625 strcpy(ics_handle, star_match[0]);
2629 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2631 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2632 DisplayIcsInteractionTitle(buf);
2633 have_set_title = TRUE;
2636 /* skip finger notes */
2637 if (started == STARTED_NONE &&
2638 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2639 (buf[i] == '1' && buf[i+1] == '0')) &&
2640 buf[i+2] == ':' && buf[i+3] == ' ') {
2641 started = STARTED_CHATTER;
2646 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2647 if(appData.seekGraph) {
2648 if(soughtPending && MatchSoughtLine(buf+i)) {
2649 i = strstr(buf+i, "rated") - buf;
2650 next_out = leftover_start = i;
2651 started = STARTED_CHATTER;
2652 suppressKibitz = TRUE;
2655 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2656 && looking_at(buf, &i, "* ads displayed")) {
2657 soughtPending = FALSE;
2662 if(appData.autoRefresh) {
2663 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2664 int s = (ics_type == ICS_ICC); // ICC format differs
2666 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2667 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2668 looking_at(buf, &i, "*% "); // eat prompt
2669 next_out = i; // suppress
2672 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2673 char *p = star_match[0];
2675 if(seekGraphUp) RemoveSeekAd(atoi(p));
2676 while(*p && *p++ != ' '); // next
2678 looking_at(buf, &i, "*% "); // eat prompt
2685 /* skip formula vars */
2686 if (started == STARTED_NONE &&
2687 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2688 started = STARTED_CHATTER;
2694 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2695 if (appData.autoKibitz && started == STARTED_NONE &&
2696 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2697 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2698 if(looking_at(buf, &i, "* kibitzes: ") &&
2699 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2700 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2701 suppressKibitz = TRUE;
2702 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2703 && (gameMode == IcsPlayingWhite)) ||
2704 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2705 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2706 started = STARTED_CHATTER; // own kibitz we simply discard
2708 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2709 parse_pos = 0; parse[0] = NULLCHAR;
2710 savingComment = TRUE;
2711 suppressKibitz = gameMode != IcsObserving ? 2 :
2712 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2716 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2717 // suppress the acknowledgements of our own autoKibitz
2719 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2720 SendToPlayer(star_match[0], strlen(star_match[0]));
2721 looking_at(buf, &i, "*% "); // eat prompt
2724 } // [HGM] kibitz: end of patch
2726 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2728 // [HGM] chat: intercept tells by users for which we have an open chat window
2730 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2731 looking_at(buf, &i, "* whispers:") ||
2732 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2733 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2734 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2735 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2737 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2738 chattingPartner = -1;
2740 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2741 for(p=0; p<MAX_CHAT; p++) {
2742 if(channel == atoi(chatPartner[p])) {
2743 talker[0] = '['; strcat(talker, "] ");
2744 chattingPartner = p; break;
2747 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2748 for(p=0; p<MAX_CHAT; p++) {
2749 if(!strcmp("WHISPER", chatPartner[p])) {
2750 talker[0] = '['; strcat(talker, "] ");
2751 chattingPartner = p; break;
2754 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2755 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2757 chattingPartner = p; break;
2759 if(chattingPartner<0) i = oldi; else {
2760 started = STARTED_COMMENT;
2761 parse_pos = 0; parse[0] = NULLCHAR;
2762 savingComment = 3 + chattingPartner; // counts as TRUE
2763 suppressKibitz = TRUE;
2765 } // [HGM] chat: end of patch
2767 if (appData.zippyTalk || appData.zippyPlay) {
2768 /* [DM] Backup address for color zippy lines */
2772 if (loggedOn == TRUE)
2773 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2774 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2776 if (ZippyControl(buf, &i) ||
2777 ZippyConverse(buf, &i) ||
2778 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2780 if (!appData.colorize) continue;
2784 } // [DM] 'else { ' deleted
2786 /* Regular tells and says */
2787 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2788 looking_at(buf, &i, "* (your partner) tells you: ") ||
2789 looking_at(buf, &i, "* says: ") ||
2790 /* Don't color "message" or "messages" output */
2791 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2792 looking_at(buf, &i, "*. * at *:*: ") ||
2793 looking_at(buf, &i, "--* (*:*): ") ||
2794 /* Message notifications (same color as tells) */
2795 looking_at(buf, &i, "* has left a message ") ||
2796 looking_at(buf, &i, "* just sent you a message:\n") ||
2797 /* Whispers and kibitzes */
2798 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2799 looking_at(buf, &i, "* kibitzes: ") ||
2801 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2803 if (tkind == 1 && strchr(star_match[0], ':')) {
2804 /* Avoid "tells you:" spoofs in channels */
2807 if (star_match[0][0] == NULLCHAR ||
2808 strchr(star_match[0], ' ') ||
2809 (tkind == 3 && strchr(star_match[1], ' '))) {
2810 /* Reject bogus matches */
2813 if (appData.colorize) {
2814 if (oldi > next_out) {
2815 SendToPlayer(&buf[next_out], oldi - next_out);
2820 Colorize(ColorTell, FALSE);
2821 curColor = ColorTell;
2824 Colorize(ColorKibitz, FALSE);
2825 curColor = ColorKibitz;
2828 p = strrchr(star_match[1], '(');
2835 Colorize(ColorChannel1, FALSE);
2836 curColor = ColorChannel1;
2838 Colorize(ColorChannel, FALSE);
2839 curColor = ColorChannel;
2843 curColor = ColorNormal;
2847 if (started == STARTED_NONE && appData.autoComment &&
2848 (gameMode == IcsObserving ||
2849 gameMode == IcsPlayingWhite ||
2850 gameMode == IcsPlayingBlack)) {
2851 parse_pos = i - oldi;
2852 memcpy(parse, &buf[oldi], parse_pos);
2853 parse[parse_pos] = NULLCHAR;
2854 started = STARTED_COMMENT;
2855 savingComment = TRUE;
2857 started = STARTED_CHATTER;
2858 savingComment = FALSE;
2865 if (looking_at(buf, &i, "* s-shouts: ") ||
2866 looking_at(buf, &i, "* c-shouts: ")) {
2867 if (appData.colorize) {
2868 if (oldi > next_out) {
2869 SendToPlayer(&buf[next_out], oldi - next_out);
2872 Colorize(ColorSShout, FALSE);
2873 curColor = ColorSShout;
2876 started = STARTED_CHATTER;
2880 if (looking_at(buf, &i, "--->")) {
2885 if (looking_at(buf, &i, "* shouts: ") ||
2886 looking_at(buf, &i, "--> ")) {
2887 if (appData.colorize) {
2888 if (oldi > next_out) {
2889 SendToPlayer(&buf[next_out], oldi - next_out);
2892 Colorize(ColorShout, FALSE);
2893 curColor = ColorShout;
2896 started = STARTED_CHATTER;
2900 if (looking_at( buf, &i, "Challenge:")) {
2901 if (appData.colorize) {
2902 if (oldi > next_out) {
2903 SendToPlayer(&buf[next_out], oldi - next_out);
2906 Colorize(ColorChallenge, FALSE);
2907 curColor = ColorChallenge;
2913 if (looking_at(buf, &i, "* offers you") ||
2914 looking_at(buf, &i, "* offers to be") ||
2915 looking_at(buf, &i, "* would like to") ||
2916 looking_at(buf, &i, "* requests to") ||
2917 looking_at(buf, &i, "Your opponent offers") ||
2918 looking_at(buf, &i, "Your opponent requests")) {
2920 if (appData.colorize) {
2921 if (oldi > next_out) {
2922 SendToPlayer(&buf[next_out], oldi - next_out);
2925 Colorize(ColorRequest, FALSE);
2926 curColor = ColorRequest;
2931 if (looking_at(buf, &i, "* (*) seeking")) {
2932 if (appData.colorize) {
2933 if (oldi > next_out) {
2934 SendToPlayer(&buf[next_out], oldi - next_out);
2937 Colorize(ColorSeek, FALSE);
2938 curColor = ColorSeek;
2943 if (looking_at(buf, &i, "\\ ")) {
2944 if (prevColor != ColorNormal) {
2945 if (oldi > next_out) {
2946 SendToPlayer(&buf[next_out], oldi - next_out);
2949 Colorize(prevColor, TRUE);
2950 curColor = prevColor;
2952 if (savingComment) {
2953 parse_pos = i - oldi;
2954 memcpy(parse, &buf[oldi], parse_pos);
2955 parse[parse_pos] = NULLCHAR;
2956 started = STARTED_COMMENT;
2957 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2958 chattingPartner = savingComment - 3; // kludge to remember the box
2960 started = STARTED_CHATTER;
2965 if (looking_at(buf, &i, "Black Strength :") ||
2966 looking_at(buf, &i, "<<< style 10 board >>>") ||
2967 looking_at(buf, &i, "<10>") ||
2968 looking_at(buf, &i, "#@#")) {
2969 /* Wrong board style */
2971 SendToICS(ics_prefix);
2972 SendToICS("set style 12\n");
2973 SendToICS(ics_prefix);
2974 SendToICS("refresh\n");
2978 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2980 have_sent_ICS_logon = 1;
2984 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2985 (looking_at(buf, &i, "\n<12> ") ||
2986 looking_at(buf, &i, "<12> "))) {
2988 if (oldi > next_out) {
2989 SendToPlayer(&buf[next_out], oldi - next_out);
2992 started = STARTED_BOARD;
2997 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2998 looking_at(buf, &i, "<b1> ")) {
2999 if (oldi > next_out) {
3000 SendToPlayer(&buf[next_out], oldi - next_out);
3003 started = STARTED_HOLDINGS;
3008 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3010 /* Header for a move list -- first line */
3012 switch (ics_getting_history) {
3016 case BeginningOfGame:
3017 /* User typed "moves" or "oldmoves" while we
3018 were idle. Pretend we asked for these
3019 moves and soak them up so user can step
3020 through them and/or save them.
3023 gameMode = IcsObserving;
3026 ics_getting_history = H_GOT_UNREQ_HEADER;
3028 case EditGame: /*?*/
3029 case EditPosition: /*?*/
3030 /* Should above feature work in these modes too? */
3031 /* For now it doesn't */
3032 ics_getting_history = H_GOT_UNWANTED_HEADER;
3035 ics_getting_history = H_GOT_UNWANTED_HEADER;
3040 /* Is this the right one? */
3041 if (gameInfo.white && gameInfo.black &&
3042 strcmp(gameInfo.white, star_match[0]) == 0 &&
3043 strcmp(gameInfo.black, star_match[2]) == 0) {
3045 ics_getting_history = H_GOT_REQ_HEADER;
3048 case H_GOT_REQ_HEADER:
3049 case H_GOT_UNREQ_HEADER:
3050 case H_GOT_UNWANTED_HEADER:
3051 case H_GETTING_MOVES:
3052 /* Should not happen */
3053 DisplayError(_("Error gathering move list: two headers"), 0);
3054 ics_getting_history = H_FALSE;
3058 /* Save player ratings into gameInfo if needed */
3059 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3060 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3061 (gameInfo.whiteRating == -1 ||
3062 gameInfo.blackRating == -1)) {
3064 gameInfo.whiteRating = string_to_rating(star_match[1]);
3065 gameInfo.blackRating = string_to_rating(star_match[3]);
3066 if (appData.debugMode)
3067 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3068 gameInfo.whiteRating, gameInfo.blackRating);
3073 if (looking_at(buf, &i,
3074 "* * match, initial time: * minute*, increment: * second")) {
3075 /* Header for a move list -- second line */
3076 /* Initial board will follow if this is a wild game */
3077 if (gameInfo.event != NULL) free(gameInfo.event);
3078 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3079 gameInfo.event = StrSave(str);
3080 /* [HGM] we switched variant. Translate boards if needed. */
3081 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3085 if (looking_at(buf, &i, "Move ")) {
3086 /* Beginning of a move list */
3087 switch (ics_getting_history) {
3089 /* Normally should not happen */
3090 /* Maybe user hit reset while we were parsing */
3093 /* Happens if we are ignoring a move list that is not
3094 * the one we just requested. Common if the user
3095 * tries to observe two games without turning off
3098 case H_GETTING_MOVES:
3099 /* Should not happen */
3100 DisplayError(_("Error gathering move list: nested"), 0);
3101 ics_getting_history = H_FALSE;
3103 case H_GOT_REQ_HEADER:
3104 ics_getting_history = H_GETTING_MOVES;
3105 started = STARTED_MOVES;
3107 if (oldi > next_out) {
3108 SendToPlayer(&buf[next_out], oldi - next_out);
3111 case H_GOT_UNREQ_HEADER:
3112 ics_getting_history = H_GETTING_MOVES;
3113 started = STARTED_MOVES_NOHIDE;
3116 case H_GOT_UNWANTED_HEADER:
3117 ics_getting_history = H_FALSE;
3123 if (looking_at(buf, &i, "% ") ||
3124 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3125 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3126 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3127 soughtPending = FALSE;
3131 if(suppressKibitz) next_out = i;
3132 savingComment = FALSE;
3136 case STARTED_MOVES_NOHIDE:
3137 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3138 parse[parse_pos + i - oldi] = NULLCHAR;
3139 ParseGameHistory(parse);
3141 if (appData.zippyPlay && first.initDone) {
3142 FeedMovesToProgram(&first, forwardMostMove);
3143 if (gameMode == IcsPlayingWhite) {
3144 if (WhiteOnMove(forwardMostMove)) {
3145 if (first.sendTime) {
3146 if (first.useColors) {
3147 SendToProgram("black\n", &first);
3149 SendTimeRemaining(&first, TRUE);
3151 if (first.useColors) {
3152 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3154 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3155 first.maybeThinking = TRUE;
3157 if (first.usePlayother) {
3158 if (first.sendTime) {
3159 SendTimeRemaining(&first, TRUE);
3161 SendToProgram("playother\n", &first);
3167 } else if (gameMode == IcsPlayingBlack) {
3168 if (!WhiteOnMove(forwardMostMove)) {
3169 if (first.sendTime) {
3170 if (first.useColors) {
3171 SendToProgram("white\n", &first);
3173 SendTimeRemaining(&first, FALSE);
3175 if (first.useColors) {
3176 SendToProgram("black\n", &first);
3178 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3179 first.maybeThinking = TRUE;
3181 if (first.usePlayother) {
3182 if (first.sendTime) {
3183 SendTimeRemaining(&first, FALSE);
3185 SendToProgram("playother\n", &first);
3194 if (gameMode == IcsObserving && ics_gamenum == -1) {
3195 /* Moves came from oldmoves or moves command
3196 while we weren't doing anything else.
3198 currentMove = forwardMostMove;
3199 ClearHighlights();/*!!could figure this out*/
3200 flipView = appData.flipView;
3201 DrawPosition(TRUE, boards[currentMove]);
3202 DisplayBothClocks();
3203 sprintf(str, "%s vs. %s",
3204 gameInfo.white, gameInfo.black);
3208 /* Moves were history of an active game */
3209 if (gameInfo.resultDetails != NULL) {
3210 free(gameInfo.resultDetails);
3211 gameInfo.resultDetails = NULL;
3214 HistorySet(parseList, backwardMostMove,
3215 forwardMostMove, currentMove-1);
3216 DisplayMove(currentMove - 1);
3217 if (started == STARTED_MOVES) next_out = i;
3218 started = STARTED_NONE;
3219 ics_getting_history = H_FALSE;
3222 case STARTED_OBSERVE:
3223 started = STARTED_NONE;
3224 SendToICS(ics_prefix);
3225 SendToICS("refresh\n");
3231 if(bookHit) { // [HGM] book: simulate book reply
3232 static char bookMove[MSG_SIZ]; // a bit generous?
3234 programStats.nodes = programStats.depth = programStats.time =
3235 programStats.score = programStats.got_only_move = 0;
3236 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3238 strcpy(bookMove, "move ");
3239 strcat(bookMove, bookHit);
3240 HandleMachineMove(bookMove, &first);
3245 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3246 started == STARTED_HOLDINGS ||
3247 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3248 /* Accumulate characters in move list or board */
3249 parse[parse_pos++] = buf[i];
3252 /* Start of game messages. Mostly we detect start of game
3253 when the first board image arrives. On some versions
3254 of the ICS, though, we need to do a "refresh" after starting
3255 to observe in order to get the current board right away. */
3256 if (looking_at(buf, &i, "Adding game * to observation list")) {
3257 started = STARTED_OBSERVE;
3261 /* Handle auto-observe */
3262 if (appData.autoObserve &&
3263 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3264 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3266 /* Choose the player that was highlighted, if any. */
3267 if (star_match[0][0] == '\033' ||
3268 star_match[1][0] != '\033') {
3269 player = star_match[0];
3271 player = star_match[2];
3273 sprintf(str, "%sobserve %s\n",
3274 ics_prefix, StripHighlightAndTitle(player));
3277 /* Save ratings from notify string */
3278 strcpy(player1Name, star_match[0]);
3279 player1Rating = string_to_rating(star_match[1]);
3280 strcpy(player2Name, star_match[2]);
3281 player2Rating = string_to_rating(star_match[3]);
3283 if (appData.debugMode)
3285 "Ratings from 'Game notification:' %s %d, %s %d\n",
3286 player1Name, player1Rating,
3287 player2Name, player2Rating);
3292 /* Deal with automatic examine mode after a game,
3293 and with IcsObserving -> IcsExamining transition */
3294 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3295 looking_at(buf, &i, "has made you an examiner of game *")) {
3297 int gamenum = atoi(star_match[0]);
3298 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3299 gamenum == ics_gamenum) {
3300 /* We were already playing or observing this game;
3301 no need to refetch history */
3302 gameMode = IcsExamining;
3304 pauseExamForwardMostMove = forwardMostMove;
3305 } else if (currentMove < forwardMostMove) {
3306 ForwardInner(forwardMostMove);
3309 /* I don't think this case really can happen */
3310 SendToICS(ics_prefix);
3311 SendToICS("refresh\n");
3316 /* Error messages */
3317 // if (ics_user_moved) {
3318 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3319 if (looking_at(buf, &i, "Illegal move") ||
3320 looking_at(buf, &i, "Not a legal move") ||
3321 looking_at(buf, &i, "Your king is in check") ||
3322 looking_at(buf, &i, "It isn't your turn") ||
3323 looking_at(buf, &i, "It is not your move")) {
3325 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3326 currentMove = forwardMostMove-1;
3327 DisplayMove(currentMove - 1); /* before DMError */
3328 DrawPosition(FALSE, boards[currentMove]);
3329 SwitchClocks(forwardMostMove-1); // [HGM] race
3330 DisplayBothClocks();
3332 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3338 if (looking_at(buf, &i, "still have time") ||
3339 looking_at(buf, &i, "not out of time") ||
3340 looking_at(buf, &i, "either player is out of time") ||
3341 looking_at(buf, &i, "has timeseal; checking")) {
3342 /* We must have called his flag a little too soon */
3343 whiteFlag = blackFlag = FALSE;
3347 if (looking_at(buf, &i, "added * seconds to") ||
3348 looking_at(buf, &i, "seconds were added to")) {
3349 /* Update the clocks */
3350 SendToICS(ics_prefix);
3351 SendToICS("refresh\n");
3355 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3356 ics_clock_paused = TRUE;
3361 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3362 ics_clock_paused = FALSE;
3367 /* Grab player ratings from the Creating: message.
3368 Note we have to check for the special case when
3369 the ICS inserts things like [white] or [black]. */
3370 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3371 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3373 0 player 1 name (not necessarily white)
3375 2 empty, white, or black (IGNORED)
3376 3 player 2 name (not necessarily black)
3379 The names/ratings are sorted out when the game
3380 actually starts (below).
3382 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3383 player1Rating = string_to_rating(star_match[1]);
3384 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3385 player2Rating = string_to_rating(star_match[4]);
3387 if (appData.debugMode)
3389 "Ratings from 'Creating:' %s %d, %s %d\n",
3390 player1Name, player1Rating,
3391 player2Name, player2Rating);
3396 /* Improved generic start/end-of-game messages */
3397 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3398 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3399 /* If tkind == 0: */
3400 /* star_match[0] is the game number */
3401 /* [1] is the white player's name */
3402 /* [2] is the black player's name */
3403 /* For end-of-game: */
3404 /* [3] is the reason for the game end */
3405 /* [4] is a PGN end game-token, preceded by " " */
3406 /* For start-of-game: */
3407 /* [3] begins with "Creating" or "Continuing" */
3408 /* [4] is " *" or empty (don't care). */
3409 int gamenum = atoi(star_match[0]);
3410 char *whitename, *blackname, *why, *endtoken;
3411 ChessMove endtype = (ChessMove) 0;
3414 whitename = star_match[1];
3415 blackname = star_match[2];
3416 why = star_match[3];
3417 endtoken = star_match[4];
3419 whitename = star_match[1];
3420 blackname = star_match[3];
3421 why = star_match[5];
3422 endtoken = star_match[6];
3425 /* Game start messages */
3426 if (strncmp(why, "Creating ", 9) == 0 ||
3427 strncmp(why, "Continuing ", 11) == 0) {
3428 gs_gamenum = gamenum;
3429 strcpy(gs_kind, strchr(why, ' ') + 1);
3430 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3432 if (appData.zippyPlay) {
3433 ZippyGameStart(whitename, blackname);
3439 /* Game end messages */
3440 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3441 ics_gamenum != gamenum) {
3444 while (endtoken[0] == ' ') endtoken++;
3445 switch (endtoken[0]) {
3448 endtype = GameUnfinished;
3451 endtype = BlackWins;
3454 if (endtoken[1] == '/')
3455 endtype = GameIsDrawn;
3457 endtype = WhiteWins;
3460 GameEnds(endtype, why, GE_ICS);
3462 if (appData.zippyPlay && first.initDone) {
3463 ZippyGameEnd(endtype, why);
3464 if (first.pr == NULL) {
3465 /* Start the next process early so that we'll
3466 be ready for the next challenge */
3467 StartChessProgram(&first);
3469 /* Send "new" early, in case this command takes
3470 a long time to finish, so that we'll be ready
3471 for the next challenge. */
3472 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3479 if (looking_at(buf, &i, "Removing game * from observation") ||
3480 looking_at(buf, &i, "no longer observing game *") ||
3481 looking_at(buf, &i, "Game * (*) has no examiners")) {
3482 if (gameMode == IcsObserving &&
3483 atoi(star_match[0]) == ics_gamenum)
3485 /* icsEngineAnalyze */
3486 if (appData.icsEngineAnalyze) {
3493 ics_user_moved = FALSE;
3498 if (looking_at(buf, &i, "no longer examining game *")) {
3499 if (gameMode == IcsExamining &&
3500 atoi(star_match[0]) == ics_gamenum)
3504 ics_user_moved = FALSE;
3509 /* Advance leftover_start past any newlines we find,
3510 so only partial lines can get reparsed */
3511 if (looking_at(buf, &i, "\n")) {
3512 prevColor = curColor;
3513 if (curColor != ColorNormal) {
3514 if (oldi > next_out) {
3515 SendToPlayer(&buf[next_out], oldi - next_out);
3518 Colorize(ColorNormal, FALSE);
3519 curColor = ColorNormal;
3521 if (started == STARTED_BOARD) {
3522 started = STARTED_NONE;
3523 parse[parse_pos] = NULLCHAR;
3524 ParseBoard12(parse);
3527 /* Send premove here */
3528 if (appData.premove) {
3530 if (currentMove == 0 &&
3531 gameMode == IcsPlayingWhite &&
3532 appData.premoveWhite) {
3533 sprintf(str, "%s\n", appData.premoveWhiteText);
3534 if (appData.debugMode)
3535 fprintf(debugFP, "Sending premove:\n");
3537 } else if (currentMove == 1 &&
3538 gameMode == IcsPlayingBlack &&
3539 appData.premoveBlack) {
3540 sprintf(str, "%s\n", appData.premoveBlackText);
3541 if (appData.debugMode)
3542 fprintf(debugFP, "Sending premove:\n");
3544 } else if (gotPremove) {
3546 ClearPremoveHighlights();
3547 if (appData.debugMode)
3548 fprintf(debugFP, "Sending premove:\n");
3549 UserMoveEvent(premoveFromX, premoveFromY,
3550 premoveToX, premoveToY,
3555 /* Usually suppress following prompt */
3556 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3557 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3558 if (looking_at(buf, &i, "*% ")) {
3559 savingComment = FALSE;
3564 } else if (started == STARTED_HOLDINGS) {
3566 char new_piece[MSG_SIZ];
3567 started = STARTED_NONE;
3568 parse[parse_pos] = NULLCHAR;
3569 if (appData.debugMode)
3570 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3571 parse, currentMove);
3572 if (sscanf(parse, " game %d", &gamenum) == 1) {
3573 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3574 if (gameInfo.variant == VariantNormal) {
3575 /* [HGM] We seem to switch variant during a game!
3576 * Presumably no holdings were displayed, so we have
3577 * to move the position two files to the right to
3578 * create room for them!
3580 VariantClass newVariant;
3581 switch(gameInfo.boardWidth) { // base guess on board width
3582 case 9: newVariant = VariantShogi; break;
3583 case 10: newVariant = VariantGreat; break;
3584 default: newVariant = VariantCrazyhouse; break;
3586 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3587 /* Get a move list just to see the header, which
3588 will tell us whether this is really bug or zh */
3589 if (ics_getting_history == H_FALSE) {
3590 ics_getting_history = H_REQUESTED;
3591 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3595 new_piece[0] = NULLCHAR;
3596 sscanf(parse, "game %d white [%s black [%s <- %s",
3597 &gamenum, white_holding, black_holding,
3599 white_holding[strlen(white_holding)-1] = NULLCHAR;
3600 black_holding[strlen(black_holding)-1] = NULLCHAR;
3601 /* [HGM] copy holdings to board holdings area */
3602 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3603 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3604 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3606 if (appData.zippyPlay && first.initDone) {
3607 ZippyHoldings(white_holding, black_holding,
3611 if (tinyLayout || smallLayout) {
3612 char wh[16], bh[16];
3613 PackHolding(wh, white_holding);
3614 PackHolding(bh, black_holding);
3615 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3616 gameInfo.white, gameInfo.black);
3618 sprintf(str, "%s [%s] vs. %s [%s]",
3619 gameInfo.white, white_holding,
3620 gameInfo.black, black_holding);
3623 DrawPosition(FALSE, boards[currentMove]);
3625 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
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 partner-board holdings area */
3632 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3633 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3634 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3637 /* Suppress following prompt */
3638 if (looking_at(buf, &i, "*% ")) {
3639 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3640 savingComment = FALSE;
3648 i++; /* skip unparsed character and loop back */
3651 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3652 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3653 // SendToPlayer(&buf[next_out], i - next_out);
3654 started != STARTED_HOLDINGS && leftover_start > next_out) {
3655 SendToPlayer(&buf[next_out], leftover_start - next_out);
3659 leftover_len = buf_len - leftover_start;
3660 /* if buffer ends with something we couldn't parse,
3661 reparse it after appending the next read */
3663 } else if (count == 0) {
3664 RemoveInputSource(isr);
3665 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3667 DisplayFatalError(_("Error reading from ICS"), error, 1);
3672 /* Board style 12 looks like this:
3674 <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
3676 * The "<12> " is stripped before it gets to this routine. The two
3677 * trailing 0's (flip state and clock ticking) are later addition, and
3678 * some chess servers may not have them, or may have only the first.
3679 * Additional trailing fields may be added in the future.
3682 #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"
3684 #define RELATION_OBSERVING_PLAYED 0
3685 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3686 #define RELATION_PLAYING_MYMOVE 1
3687 #define RELATION_PLAYING_NOTMYMOVE -1
3688 #define RELATION_EXAMINING 2
3689 #define RELATION_ISOLATED_BOARD -3
3690 #define RELATION_STARTING_POSITION -4 /* FICS only */
3693 ParseBoard12(string)
3696 GameMode newGameMode;
3697 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3698 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3699 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3700 char to_play, board_chars[200];
3701 char move_str[500], str[500], elapsed_time[500];
3702 char black[32], white[32];
3704 int prevMove = currentMove;
3707 int fromX, fromY, toX, toY;
3709 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3710 char *bookHit = NULL; // [HGM] book
3711 Boolean weird = FALSE, reqFlag = FALSE;
3713 fromX = fromY = toX = toY = -1;
3717 if (appData.debugMode)
3718 fprintf(debugFP, _("Parsing board: %s\n"), string);
3720 move_str[0] = NULLCHAR;
3721 elapsed_time[0] = NULLCHAR;
3722 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3724 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3725 if(string[i] == ' ') { ranks++; files = 0; }
3727 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3730 for(j = 0; j <i; j++) board_chars[j] = string[j];
3731 board_chars[i] = '\0';
3734 n = sscanf(string, PATTERN, &to_play, &double_push,
3735 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3736 &gamenum, white, black, &relation, &basetime, &increment,
3737 &white_stren, &black_stren, &white_time, &black_time,
3738 &moveNum, str, elapsed_time, move_str, &ics_flip,
3742 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3743 DisplayError(str, 0);
3747 /* Convert the move number to internal form */
3748 moveNum = (moveNum - 1) * 2;
3749 if (to_play == 'B') moveNum++;
3750 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3751 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3757 case RELATION_OBSERVING_PLAYED:
3758 case RELATION_OBSERVING_STATIC:
3759 if (gamenum == -1) {
3760 /* Old ICC buglet */
3761 relation = RELATION_OBSERVING_STATIC;
3763 newGameMode = IcsObserving;
3765 case RELATION_PLAYING_MYMOVE:
3766 case RELATION_PLAYING_NOTMYMOVE:
3768 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3769 IcsPlayingWhite : IcsPlayingBlack;
3771 case RELATION_EXAMINING:
3772 newGameMode = IcsExamining;
3774 case RELATION_ISOLATED_BOARD:
3776 /* Just display this board. If user was doing something else,
3777 we will forget about it until the next board comes. */
3778 newGameMode = IcsIdle;
3780 case RELATION_STARTING_POSITION:
3781 newGameMode = gameMode;
3785 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3786 && newGameMode == IcsObserving && appData.bgObserve) {
3787 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3789 for (k = 0; k < ranks; k++) {
3790 for (j = 0; j < files; j++)
3791 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3792 if(gameInfo.holdingsWidth > 1) {
3793 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3794 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3797 CopyBoard(partnerBoard, board);
3798 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3799 sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3800 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3801 DisplayMessage(buf, "");
3805 /* Modify behavior for initial board display on move listing
3808 switch (ics_getting_history) {
3812 case H_GOT_REQ_HEADER:
3813 case H_GOT_UNREQ_HEADER:
3814 /* This is the initial position of the current game */
3815 gamenum = ics_gamenum;
3816 moveNum = 0; /* old ICS bug workaround */
3817 if (to_play == 'B') {
3818 startedFromSetupPosition = TRUE;
3819 blackPlaysFirst = TRUE;
3821 if (forwardMostMove == 0) forwardMostMove = 1;
3822 if (backwardMostMove == 0) backwardMostMove = 1;
3823 if (currentMove == 0) currentMove = 1;
3825 newGameMode = gameMode;
3826 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3828 case H_GOT_UNWANTED_HEADER:
3829 /* This is an initial board that we don't want */
3831 case H_GETTING_MOVES:
3832 /* Should not happen */
3833 DisplayError(_("Error gathering move list: extra board"), 0);
3834 ics_getting_history = H_FALSE;
3838 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3839 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3840 /* [HGM] We seem to have switched variant unexpectedly
3841 * Try to guess new variant from board size
3843 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3844 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3845 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3846 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3847 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3848 if(!weird) newVariant = VariantNormal;
3849 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3850 /* Get a move list just to see the header, which
3851 will tell us whether this is really bug or zh */
3852 if (ics_getting_history == H_FALSE) {
3853 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3854 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3859 /* Take action if this is the first board of a new game, or of a
3860 different game than is currently being displayed. */
3861 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3862 relation == RELATION_ISOLATED_BOARD) {
3864 /* Forget the old game and get the history (if any) of the new one */
3865 if (gameMode != BeginningOfGame) {
3869 if (appData.autoRaiseBoard) BoardToTop();
3871 if (gamenum == -1) {
3872 newGameMode = IcsIdle;
3873 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3874 appData.getMoveList && !reqFlag) {
3875 /* Need to get game history */
3876 ics_getting_history = H_REQUESTED;
3877 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3881 /* Initially flip the board to have black on the bottom if playing
3882 black or if the ICS flip flag is set, but let the user change
3883 it with the Flip View button. */
3884 flipView = appData.autoFlipView ?
3885 (newGameMode == IcsPlayingBlack) || ics_flip :
3888 /* Done with values from previous mode; copy in new ones */
3889 gameMode = newGameMode;
3891 ics_gamenum = gamenum;
3892 if (gamenum == gs_gamenum) {
3893 int klen = strlen(gs_kind);
3894 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3895 sprintf(str, "ICS %s", gs_kind);
3896 gameInfo.event = StrSave(str);
3898 gameInfo.event = StrSave("ICS game");
3900 gameInfo.site = StrSave(appData.icsHost);
3901 gameInfo.date = PGNDate();
3902 gameInfo.round = StrSave("-");
3903 gameInfo.white = StrSave(white);
3904 gameInfo.black = StrSave(black);
3905 timeControl = basetime * 60 * 1000;
3907 timeIncrement = increment * 1000;
3908 movesPerSession = 0;
3909 gameInfo.timeControl = TimeControlTagValue();
3910 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3911 if (appData.debugMode) {
3912 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3913 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3914 setbuf(debugFP, NULL);
3917 gameInfo.outOfBook = NULL;
3919 /* Do we have the ratings? */
3920 if (strcmp(player1Name, white) == 0 &&
3921 strcmp(player2Name, black) == 0) {
3922 if (appData.debugMode)
3923 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3924 player1Rating, player2Rating);
3925 gameInfo.whiteRating = player1Rating;
3926 gameInfo.blackRating = player2Rating;
3927 } else if (strcmp(player2Name, white) == 0 &&
3928 strcmp(player1Name, black) == 0) {
3929 if (appData.debugMode)
3930 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3931 player2Rating, player1Rating);
3932 gameInfo.whiteRating = player2Rating;
3933 gameInfo.blackRating = player1Rating;
3935 player1Name[0] = player2Name[0] = NULLCHAR;
3937 /* Silence shouts if requested */
3938 if (appData.quietPlay &&
3939 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3940 SendToICS(ics_prefix);
3941 SendToICS("set shout 0\n");
3945 /* Deal with midgame name changes */
3947 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3948 if (gameInfo.white) free(gameInfo.white);
3949 gameInfo.white = StrSave(white);
3951 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3952 if (gameInfo.black) free(gameInfo.black);
3953 gameInfo.black = StrSave(black);
3957 /* Throw away game result if anything actually changes in examine mode */
3958 if (gameMode == IcsExamining && !newGame) {
3959 gameInfo.result = GameUnfinished;
3960 if (gameInfo.resultDetails != NULL) {
3961 free(gameInfo.resultDetails);
3962 gameInfo.resultDetails = NULL;
3966 /* In pausing && IcsExamining mode, we ignore boards coming
3967 in if they are in a different variation than we are. */
3968 if (pauseExamInvalid) return;
3969 if (pausing && gameMode == IcsExamining) {
3970 if (moveNum <= pauseExamForwardMostMove) {
3971 pauseExamInvalid = TRUE;
3972 forwardMostMove = pauseExamForwardMostMove;
3977 if (appData.debugMode) {
3978 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3980 /* Parse the board */
3981 for (k = 0; k < ranks; k++) {
3982 for (j = 0; j < files; j++)
3983 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3984 if(gameInfo.holdingsWidth > 1) {
3985 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3986 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3989 CopyBoard(boards[moveNum], board);
3990 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3992 startedFromSetupPosition =
3993 !CompareBoards(board, initialPosition);
3994 if(startedFromSetupPosition)
3995 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3998 /* [HGM] Set castling rights. Take the outermost Rooks,
3999 to make it also work for FRC opening positions. Note that board12
4000 is really defective for later FRC positions, as it has no way to
4001 indicate which Rook can castle if they are on the same side of King.
4002 For the initial position we grant rights to the outermost Rooks,
4003 and remember thos rights, and we then copy them on positions
4004 later in an FRC game. This means WB might not recognize castlings with
4005 Rooks that have moved back to their original position as illegal,
4006 but in ICS mode that is not its job anyway.
4008 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4009 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4011 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4012 if(board[0][i] == WhiteRook) j = i;
4013 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4014 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4015 if(board[0][i] == WhiteRook) j = i;
4016 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4017 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4018 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4019 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4020 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4021 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4022 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4024 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4025 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4026 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4027 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4028 if(board[BOARD_HEIGHT-1][k] == bKing)
4029 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4030 if(gameInfo.variant == VariantTwoKings) {
4031 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4032 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4033 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4036 r = boards[moveNum][CASTLING][0] = initialRights[0];
4037 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4038 r = boards[moveNum][CASTLING][1] = initialRights[1];
4039 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4040 r = boards[moveNum][CASTLING][3] = initialRights[3];
4041 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4042 r = boards[moveNum][CASTLING][4] = initialRights[4];
4043 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4044 /* wildcastle kludge: always assume King has rights */
4045 r = boards[moveNum][CASTLING][2] = initialRights[2];
4046 r = boards[moveNum][CASTLING][5] = initialRights[5];
4048 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4049 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4052 if (ics_getting_history == H_GOT_REQ_HEADER ||
4053 ics_getting_history == H_GOT_UNREQ_HEADER) {
4054 /* This was an initial position from a move list, not
4055 the current position */
4059 /* Update currentMove and known move number limits */
4060 newMove = newGame || moveNum > forwardMostMove;
4063 forwardMostMove = backwardMostMove = currentMove = moveNum;
4064 if (gameMode == IcsExamining && moveNum == 0) {
4065 /* Workaround for ICS limitation: we are not told the wild
4066 type when starting to examine a game. But if we ask for
4067 the move list, the move list header will tell us */
4068 ics_getting_history = H_REQUESTED;
4069 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4072 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4073 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4075 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4076 /* [HGM] applied this also to an engine that is silently watching */
4077 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4078 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4079 gameInfo.variant == currentlyInitializedVariant) {
4080 takeback = forwardMostMove - moveNum;
4081 for (i = 0; i < takeback; i++) {
4082 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4083 SendToProgram("undo\n", &first);
4088 forwardMostMove = moveNum;
4089 if (!pausing || currentMove > forwardMostMove)
4090 currentMove = forwardMostMove;
4092 /* New part of history that is not contiguous with old part */
4093 if (pausing && gameMode == IcsExamining) {
4094 pauseExamInvalid = TRUE;
4095 forwardMostMove = pauseExamForwardMostMove;
4098 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4100 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4101 // [HGM] when we will receive the move list we now request, it will be
4102 // fed to the engine from the first move on. So if the engine is not
4103 // in the initial position now, bring it there.
4104 InitChessProgram(&first, 0);
4107 ics_getting_history = H_REQUESTED;
4108 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4111 forwardMostMove = backwardMostMove = currentMove = moveNum;
4114 /* Update the clocks */
4115 if (strchr(elapsed_time, '.')) {
4117 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4118 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4120 /* Time is in seconds */
4121 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4122 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4127 if (appData.zippyPlay && newGame &&
4128 gameMode != IcsObserving && gameMode != IcsIdle &&
4129 gameMode != IcsExamining)
4130 ZippyFirstBoard(moveNum, basetime, increment);
4133 /* Put the move on the move list, first converting
4134 to canonical algebraic form. */
4136 if (appData.debugMode) {
4137 if (appData.debugMode) { int f = forwardMostMove;
4138 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4139 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4140 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4142 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4143 fprintf(debugFP, "moveNum = %d\n", moveNum);
4144 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4145 setbuf(debugFP, NULL);
4147 if (moveNum <= backwardMostMove) {
4148 /* We don't know what the board looked like before
4150 strcpy(parseList[moveNum - 1], move_str);
4151 strcat(parseList[moveNum - 1], " ");
4152 strcat(parseList[moveNum - 1], elapsed_time);
4153 moveList[moveNum - 1][0] = NULLCHAR;
4154 } else if (strcmp(move_str, "none") == 0) {
4155 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4156 /* Again, we don't know what the board looked like;
4157 this is really the start of the game. */
4158 parseList[moveNum - 1][0] = NULLCHAR;
4159 moveList[moveNum - 1][0] = NULLCHAR;
4160 backwardMostMove = moveNum;
4161 startedFromSetupPosition = TRUE;
4162 fromX = fromY = toX = toY = -1;
4164 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4165 // So we parse the long-algebraic move string in stead of the SAN move
4166 int valid; char buf[MSG_SIZ], *prom;
4168 // str looks something like "Q/a1-a2"; kill the slash
4170 sprintf(buf, "%c%s", str[0], str+2);
4171 else strcpy(buf, str); // might be castling
4172 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4173 strcat(buf, prom); // long move lacks promo specification!
4174 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4175 if(appData.debugMode)
4176 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4177 strcpy(move_str, buf);
4179 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4180 &fromX, &fromY, &toX, &toY, &promoChar)
4181 || ParseOneMove(buf, moveNum - 1, &moveType,
4182 &fromX, &fromY, &toX, &toY, &promoChar);
4183 // end of long SAN patch
4185 (void) CoordsToAlgebraic(boards[moveNum - 1],
4186 PosFlags(moveNum - 1),
4187 fromY, fromX, toY, toX, promoChar,
4188 parseList[moveNum-1]);
4189 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4195 if(gameInfo.variant != VariantShogi)
4196 strcat(parseList[moveNum - 1], "+");
4199 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4200 strcat(parseList[moveNum - 1], "#");
4203 strcat(parseList[moveNum - 1], " ");
4204 strcat(parseList[moveNum - 1], elapsed_time);
4205 /* currentMoveString is set as a side-effect of ParseOneMove */
4206 strcpy(moveList[moveNum - 1], currentMoveString);
4207 strcat(moveList[moveNum - 1], "\n");
4209 /* Move from ICS was illegal!? Punt. */
4210 if (appData.debugMode) {
4211 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4212 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4214 strcpy(parseList[moveNum - 1], move_str);
4215 strcat(parseList[moveNum - 1], " ");
4216 strcat(parseList[moveNum - 1], elapsed_time);
4217 moveList[moveNum - 1][0] = NULLCHAR;
4218 fromX = fromY = toX = toY = -1;
4221 if (appData.debugMode) {
4222 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4223 setbuf(debugFP, NULL);
4227 /* Send move to chess program (BEFORE animating it). */
4228 if (appData.zippyPlay && !newGame && newMove &&
4229 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4231 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4232 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4233 if (moveList[moveNum - 1][0] == NULLCHAR) {
4234 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4236 DisplayError(str, 0);
4238 if (first.sendTime) {
4239 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4241 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4242 if (firstMove && !bookHit) {
4244 if (first.useColors) {
4245 SendToProgram(gameMode == IcsPlayingWhite ?
4247 "black\ngo\n", &first);
4249 SendToProgram("go\n", &first);
4251 first.maybeThinking = TRUE;
4254 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4255 if (moveList[moveNum - 1][0] == NULLCHAR) {
4256 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4257 DisplayError(str, 0);
4259 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4260 SendMoveToProgram(moveNum - 1, &first);
4267 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4268 /* If move comes from a remote source, animate it. If it
4269 isn't remote, it will have already been animated. */
4270 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4271 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4273 if (!pausing && appData.highlightLastMove) {
4274 SetHighlights(fromX, fromY, toX, toY);
4278 /* Start the clocks */
4279 whiteFlag = blackFlag = FALSE;
4280 appData.clockMode = !(basetime == 0 && increment == 0);
4282 ics_clock_paused = TRUE;
4284 } else if (ticking == 1) {
4285 ics_clock_paused = FALSE;
4287 if (gameMode == IcsIdle ||
4288 relation == RELATION_OBSERVING_STATIC ||
4289 relation == RELATION_EXAMINING ||
4291 DisplayBothClocks();
4295 /* Display opponents and material strengths */
4296 if (gameInfo.variant != VariantBughouse &&
4297 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4298 if (tinyLayout || smallLayout) {
4299 if(gameInfo.variant == VariantNormal)
4300 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4301 gameInfo.white, white_stren, gameInfo.black, black_stren,
4302 basetime, increment);
4304 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4305 gameInfo.white, white_stren, gameInfo.black, black_stren,
4306 basetime, increment, (int) gameInfo.variant);
4308 if(gameInfo.variant == VariantNormal)
4309 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4310 gameInfo.white, white_stren, gameInfo.black, black_stren,
4311 basetime, increment);
4313 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4314 gameInfo.white, white_stren, gameInfo.black, black_stren,
4315 basetime, increment, VariantName(gameInfo.variant));
4318 if (appData.debugMode) {
4319 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4324 /* Display the board */
4325 if (!pausing && !appData.noGUI) {
4327 if (appData.premove)
4329 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4330 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4331 ClearPremoveHighlights();
4333 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4334 DrawPosition(j, boards[currentMove]);
4336 DisplayMove(moveNum - 1);
4337 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4338 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4339 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4340 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4344 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4346 if(bookHit) { // [HGM] book: simulate book reply
4347 static char bookMove[MSG_SIZ]; // a bit generous?
4349 programStats.nodes = programStats.depth = programStats.time =
4350 programStats.score = programStats.got_only_move = 0;
4351 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4353 strcpy(bookMove, "move ");
4354 strcat(bookMove, bookHit);
4355 HandleMachineMove(bookMove, &first);
4364 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4365 ics_getting_history = H_REQUESTED;
4366 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4372 AnalysisPeriodicEvent(force)
4375 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4376 && !force) || !appData.periodicUpdates)
4379 /* Send . command to Crafty to collect stats */
4380 SendToProgram(".\n", &first);
4382 /* Don't send another until we get a response (this makes
4383 us stop sending to old Crafty's which don't understand
4384 the "." command (sending illegal cmds resets node count & time,
4385 which looks bad)) */
4386 programStats.ok_to_send = 0;
4389 void ics_update_width(new_width)
4392 ics_printf("set width %d\n", new_width);
4396 SendMoveToProgram(moveNum, cps)
4398 ChessProgramState *cps;
4402 if (cps->useUsermove) {
4403 SendToProgram("usermove ", cps);
4407 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4408 int len = space - parseList[moveNum];
4409 memcpy(buf, parseList[moveNum], len);
4411 buf[len] = NULLCHAR;
4413 sprintf(buf, "%s\n", parseList[moveNum]);
4415 SendToProgram(buf, cps);
4417 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4418 AlphaRank(moveList[moveNum], 4);
4419 SendToProgram(moveList[moveNum], cps);
4420 AlphaRank(moveList[moveNum], 4); // and back
4422 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4423 * the engine. It would be nice to have a better way to identify castle
4425 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4426 && cps->useOOCastle) {
4427 int fromX = moveList[moveNum][0] - AAA;
4428 int fromY = moveList[moveNum][1] - ONE;
4429 int toX = moveList[moveNum][2] - AAA;
4430 int toY = moveList[moveNum][3] - ONE;
4431 if((boards[moveNum][fromY][fromX] == WhiteKing
4432 && boards[moveNum][toY][toX] == WhiteRook)
4433 || (boards[moveNum][fromY][fromX] == BlackKing
4434 && boards[moveNum][toY][toX] == BlackRook)) {
4435 if(toX > fromX) SendToProgram("O-O\n", cps);
4436 else SendToProgram("O-O-O\n", cps);
4438 else SendToProgram(moveList[moveNum], cps);
4440 else SendToProgram(moveList[moveNum], cps);
4441 /* End of additions by Tord */
4444 /* [HGM] setting up the opening has brought engine in force mode! */
4445 /* Send 'go' if we are in a mode where machine should play. */
4446 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4447 (gameMode == TwoMachinesPlay ||
4449 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4451 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4452 SendToProgram("go\n", cps);
4453 if (appData.debugMode) {
4454 fprintf(debugFP, "(extra)\n");
4457 setboardSpoiledMachineBlack = 0;
4461 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4463 int fromX, fromY, toX, toY;
4465 char user_move[MSG_SIZ];
4469 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4470 (int)moveType, fromX, fromY, toX, toY);
4471 DisplayError(user_move + strlen("say "), 0);
4473 case WhiteKingSideCastle:
4474 case BlackKingSideCastle:
4475 case WhiteQueenSideCastleWild:
4476 case BlackQueenSideCastleWild:
4478 case WhiteHSideCastleFR:
4479 case BlackHSideCastleFR:
4481 sprintf(user_move, "o-o\n");
4483 case WhiteQueenSideCastle:
4484 case BlackQueenSideCastle:
4485 case WhiteKingSideCastleWild:
4486 case BlackKingSideCastleWild:
4488 case WhiteASideCastleFR:
4489 case BlackASideCastleFR:
4491 sprintf(user_move, "o-o-o\n");
4493 case WhitePromotionQueen:
4494 case BlackPromotionQueen:
4495 case WhitePromotionRook:
4496 case BlackPromotionRook:
4497 case WhitePromotionBishop:
4498 case BlackPromotionBishop:
4499 case WhitePromotionKnight:
4500 case BlackPromotionKnight:
4501 case WhitePromotionKing:
4502 case BlackPromotionKing:
4503 case WhitePromotionChancellor:
4504 case BlackPromotionChancellor:
4505 case WhitePromotionArchbishop:
4506 case BlackPromotionArchbishop:
4507 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4508 sprintf(user_move, "%c%c%c%c=%c\n",
4509 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4510 PieceToChar(WhiteFerz));
4511 else if(gameInfo.variant == VariantGreat)
4512 sprintf(user_move, "%c%c%c%c=%c\n",
4513 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4514 PieceToChar(WhiteMan));
4516 sprintf(user_move, "%c%c%c%c=%c\n",
4517 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4518 PieceToChar(PromoPiece(moveType)));
4522 sprintf(user_move, "%c@%c%c\n",
4523 ToUpper(PieceToChar((ChessSquare) fromX)),
4524 AAA + toX, ONE + toY);
4527 case WhiteCapturesEnPassant:
4528 case BlackCapturesEnPassant:
4529 case IllegalMove: /* could be a variant we don't quite understand */
4530 sprintf(user_move, "%c%c%c%c\n",
4531 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4534 SendToICS(user_move);
4535 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4536 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4540 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4545 if (rf == DROP_RANK) {
4546 sprintf(move, "%c@%c%c\n",
4547 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4549 if (promoChar == 'x' || promoChar == NULLCHAR) {
4550 sprintf(move, "%c%c%c%c\n",
4551 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4553 sprintf(move, "%c%c%c%c%c\n",
4554 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4560 ProcessICSInitScript(f)
4565 while (fgets(buf, MSG_SIZ, f)) {
4566 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4573 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4575 AlphaRank(char *move, int n)
4577 // char *p = move, c; int x, y;
4579 if (appData.debugMode) {
4580 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4584 move[2]>='0' && move[2]<='9' &&
4585 move[3]>='a' && move[3]<='x' ) {
4587 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4588 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4590 if(move[0]>='0' && move[0]<='9' &&
4591 move[1]>='a' && move[1]<='x' &&
4592 move[2]>='0' && move[2]<='9' &&
4593 move[3]>='a' && move[3]<='x' ) {
4594 /* input move, Shogi -> normal */
4595 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4596 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4597 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4598 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4601 move[3]>='0' && move[3]<='9' &&
4602 move[2]>='a' && move[2]<='x' ) {
4604 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4605 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4608 move[0]>='a' && move[0]<='x' &&
4609 move[3]>='0' && move[3]<='9' &&
4610 move[2]>='a' && move[2]<='x' ) {
4611 /* output move, normal -> Shogi */
4612 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4613 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4614 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4615 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4616 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4618 if (appData.debugMode) {
4619 fprintf(debugFP, " out = '%s'\n", move);
4623 /* Parser for moves from gnuchess, ICS, or user typein box */
4625 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4628 ChessMove *moveType;
4629 int *fromX, *fromY, *toX, *toY;
4632 if (appData.debugMode) {
4633 fprintf(debugFP, "move to parse: %s\n", move);
4635 *moveType = yylexstr(moveNum, move);
4637 switch (*moveType) {
4638 case WhitePromotionChancellor:
4639 case BlackPromotionChancellor:
4640 case WhitePromotionArchbishop:
4641 case BlackPromotionArchbishop:
4642 case WhitePromotionQueen:
4643 case BlackPromotionQueen:
4644 case WhitePromotionRook:
4645 case BlackPromotionRook:
4646 case WhitePromotionBishop:
4647 case BlackPromotionBishop:
4648 case WhitePromotionKnight:
4649 case BlackPromotionKnight:
4650 case WhitePromotionKing:
4651 case BlackPromotionKing:
4653 case WhiteCapturesEnPassant:
4654 case BlackCapturesEnPassant:
4655 case WhiteKingSideCastle:
4656 case WhiteQueenSideCastle:
4657 case BlackKingSideCastle:
4658 case BlackQueenSideCastle:
4659 case WhiteKingSideCastleWild:
4660 case WhiteQueenSideCastleWild:
4661 case BlackKingSideCastleWild:
4662 case BlackQueenSideCastleWild:
4663 /* Code added by Tord: */
4664 case WhiteHSideCastleFR:
4665 case WhiteASideCastleFR:
4666 case BlackHSideCastleFR:
4667 case BlackASideCastleFR:
4668 /* End of code added by Tord */
4669 case IllegalMove: /* bug or odd chess variant */
4670 *fromX = currentMoveString[0] - AAA;
4671 *fromY = currentMoveString[1] - ONE;
4672 *toX = currentMoveString[2] - AAA;
4673 *toY = currentMoveString[3] - ONE;
4674 *promoChar = currentMoveString[4];
4675 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4676 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4677 if (appData.debugMode) {
4678 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4680 *fromX = *fromY = *toX = *toY = 0;
4683 if (appData.testLegality) {
4684 return (*moveType != IllegalMove);
4686 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4687 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4692 *fromX = *moveType == WhiteDrop ?
4693 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4694 (int) CharToPiece(ToLower(currentMoveString[0]));
4696 *toX = currentMoveString[2] - AAA;
4697 *toY = currentMoveString[3] - ONE;
4698 *promoChar = NULLCHAR;
4702 case ImpossibleMove:
4703 case (ChessMove) 0: /* end of file */
4712 if (appData.debugMode) {
4713 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4716 *fromX = *fromY = *toX = *toY = 0;
4717 *promoChar = NULLCHAR;
4725 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4726 int fromX, fromY, toX, toY; char promoChar;
4731 endPV = forwardMostMove;
4733 while(*pv == ' ') pv++;
4734 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4735 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4736 if(appData.debugMode){
4737 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4739 if(!valid && nr == 0 &&
4740 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4741 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4743 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4744 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4746 if(endPV+1 > framePtr) break; // no space, truncate
4749 CopyBoard(boards[endPV], boards[endPV-1]);
4750 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4751 moveList[endPV-1][0] = fromX + AAA;
4752 moveList[endPV-1][1] = fromY + ONE;
4753 moveList[endPV-1][2] = toX + AAA;
4754 moveList[endPV-1][3] = toY + ONE;
4755 parseList[endPV-1][0] = NULLCHAR;
4757 currentMove = endPV;
4758 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4759 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4760 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4761 DrawPosition(TRUE, boards[currentMove]);
4764 static int lastX, lastY;
4767 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4771 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4772 lastX = x; lastY = y;
4773 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4775 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4777 while(buf[index] && buf[index] != '\n') index++;
4779 ParsePV(buf+startPV);
4780 *start = startPV; *end = index-1;
4785 LoadPV(int x, int y)
4786 { // called on right mouse click to load PV
4787 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4788 lastX = x; lastY = y;
4789 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4796 if(endPV < 0) return;
4798 currentMove = forwardMostMove;
4799 ClearPremoveHighlights();
4800 DrawPosition(TRUE, boards[currentMove]);
4804 MovePV(int x, int y, int h)
4805 { // step through PV based on mouse coordinates (called on mouse move)
4806 int margin = h>>3, step = 0;
4808 if(endPV < 0) return;
4809 // we must somehow check if right button is still down (might be released off board!)
4810 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4811 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4812 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4814 lastX = x; lastY = y;
4815 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4816 currentMove += step;
4817 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4818 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4819 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4820 DrawPosition(FALSE, boards[currentMove]);
4824 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4825 // All positions will have equal probability, but the current method will not provide a unique
4826 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4832 int piecesLeft[(int)BlackPawn];
4833 int seed, nrOfShuffles;
4835 void GetPositionNumber()
4836 { // sets global variable seed
4839 seed = appData.defaultFrcPosition;
4840 if(seed < 0) { // randomize based on time for negative FRC position numbers
4841 for(i=0; i<50; i++) seed += random();
4842 seed = random() ^ random() >> 8 ^ random() << 8;
4843 if(seed<0) seed = -seed;
4847 int put(Board board, int pieceType, int rank, int n, int shade)
4848 // put the piece on the (n-1)-th empty squares of the given shade
4852 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4853 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4854 board[rank][i] = (ChessSquare) pieceType;
4855 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4857 piecesLeft[pieceType]--;
4865 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4866 // calculate where the next piece goes, (any empty square), and put it there
4870 i = seed % squaresLeft[shade];
4871 nrOfShuffles *= squaresLeft[shade];
4872 seed /= squaresLeft[shade];
4873 put(board, pieceType, rank, i, shade);
4876 void AddTwoPieces(Board board, int pieceType, int rank)
4877 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4879 int i, n=squaresLeft[ANY], j=n-1, k;
4881 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4882 i = seed % k; // pick one
4885 while(i >= j) i -= j--;
4886 j = n - 1 - j; i += j;
4887 put(board, pieceType, rank, j, ANY);
4888 put(board, pieceType, rank, i, ANY);
4891 void SetUpShuffle(Board board, int number)
4895 GetPositionNumber(); nrOfShuffles = 1;
4897 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4898 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4899 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4901 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4903 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4904 p = (int) board[0][i];
4905 if(p < (int) BlackPawn) piecesLeft[p] ++;
4906 board[0][i] = EmptySquare;
4909 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4910 // shuffles restricted to allow normal castling put KRR first
4911 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4912 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4913 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4914 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4915 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4916 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4917 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4918 put(board, WhiteRook, 0, 0, ANY);
4919 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4922 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4923 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4924 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4925 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4926 while(piecesLeft[p] >= 2) {
4927 AddOnePiece(board, p, 0, LITE);
4928 AddOnePiece(board, p, 0, DARK);
4930 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4933 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4934 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4935 // but we leave King and Rooks for last, to possibly obey FRC restriction
4936 if(p == (int)WhiteRook) continue;
4937 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4938 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4941 // now everything is placed, except perhaps King (Unicorn) and Rooks
4943 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4944 // Last King gets castling rights
4945 while(piecesLeft[(int)WhiteUnicorn]) {
4946 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4947 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4950 while(piecesLeft[(int)WhiteKing]) {
4951 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4952 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4957 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4958 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4961 // Only Rooks can be left; simply place them all
4962 while(piecesLeft[(int)WhiteRook]) {
4963 i = put(board, WhiteRook, 0, 0, ANY);
4964 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4967 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4969 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4972 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4973 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4976 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4979 int SetCharTable( char *table, const char * map )
4980 /* [HGM] moved here from winboard.c because of its general usefulness */
4981 /* Basically a safe strcpy that uses the last character as King */
4983 int result = FALSE; int NrPieces;
4985 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4986 && NrPieces >= 12 && !(NrPieces&1)) {
4987 int i; /* [HGM] Accept even length from 12 to 34 */
4989 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4990 for( i=0; i<NrPieces/2-1; i++ ) {
4992 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4994 table[(int) WhiteKing] = map[NrPieces/2-1];
4995 table[(int) BlackKing] = map[NrPieces-1];
5003 void Prelude(Board board)
5004 { // [HGM] superchess: random selection of exo-pieces
5005 int i, j, k; ChessSquare p;
5006 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5008 GetPositionNumber(); // use FRC position number
5010 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5011 SetCharTable(pieceToChar, appData.pieceToCharTable);
5012 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5013 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5016 j = seed%4; seed /= 4;
5017 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5018 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5019 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5020 j = seed%3 + (seed%3 >= j); seed /= 3;
5021 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5022 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5023 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5024 j = seed%3; seed /= 3;
5025 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5026 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5027 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5028 j = seed%2 + (seed%2 >= j); seed /= 2;
5029 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5030 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5031 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5032 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5033 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5034 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5035 put(board, exoPieces[0], 0, 0, ANY);
5036 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5040 InitPosition(redraw)
5043 ChessSquare (* pieces)[BOARD_FILES];
5044 int i, j, pawnRow, overrule,
5045 oldx = gameInfo.boardWidth,
5046 oldy = gameInfo.boardHeight,
5047 oldh = gameInfo.holdingsWidth,
5048 oldv = gameInfo.variant;
5050 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5052 /* [AS] Initialize pv info list [HGM] and game status */
5054 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5055 pvInfoList[i].depth = 0;
5056 boards[i][EP_STATUS] = EP_NONE;
5057 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5060 initialRulePlies = 0; /* 50-move counter start */
5062 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5063 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5067 /* [HGM] logic here is completely changed. In stead of full positions */
5068 /* the initialized data only consist of the two backranks. The switch */
5069 /* selects which one we will use, which is than copied to the Board */
5070 /* initialPosition, which for the rest is initialized by Pawns and */
5071 /* empty squares. This initial position is then copied to boards[0], */
5072 /* possibly after shuffling, so that it remains available. */
5074 gameInfo.holdingsWidth = 0; /* default board sizes */
5075 gameInfo.boardWidth = 8;
5076 gameInfo.boardHeight = 8;
5077 gameInfo.holdingsSize = 0;
5078 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5079 for(i=0; i<BOARD_FILES-2; i++)
5080 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5081 initialPosition[EP_STATUS] = EP_NONE;
5082 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5084 switch (gameInfo.variant) {
5085 case VariantFischeRandom:
5086 shuffleOpenings = TRUE;
5090 case VariantShatranj:
5091 pieces = ShatranjArray;
5092 nrCastlingRights = 0;
5093 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5096 pieces = makrukArray;
5097 nrCastlingRights = 0;
5098 startedFromSetupPosition = TRUE;
5099 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5101 case VariantTwoKings:
5102 pieces = twoKingsArray;
5104 case VariantCapaRandom:
5105 shuffleOpenings = TRUE;
5106 case VariantCapablanca:
5107 pieces = CapablancaArray;
5108 gameInfo.boardWidth = 10;
5109 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5112 pieces = GothicArray;
5113 gameInfo.boardWidth = 10;
5114 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5117 pieces = JanusArray;
5118 gameInfo.boardWidth = 10;
5119 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5120 nrCastlingRights = 6;
5121 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5122 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5123 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5124 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5125 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5126 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5129 pieces = FalconArray;
5130 gameInfo.boardWidth = 10;
5131 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5133 case VariantXiangqi:
5134 pieces = XiangqiArray;
5135 gameInfo.boardWidth = 9;
5136 gameInfo.boardHeight = 10;
5137 nrCastlingRights = 0;
5138 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5141 pieces = ShogiArray;
5142 gameInfo.boardWidth = 9;
5143 gameInfo.boardHeight = 9;
5144 gameInfo.holdingsSize = 7;
5145 nrCastlingRights = 0;
5146 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5148 case VariantCourier:
5149 pieces = CourierArray;
5150 gameInfo.boardWidth = 12;
5151 nrCastlingRights = 0;
5152 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5154 case VariantKnightmate:
5155 pieces = KnightmateArray;
5156 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5159 pieces = fairyArray;
5160 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5163 pieces = GreatArray;
5164 gameInfo.boardWidth = 10;
5165 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5166 gameInfo.holdingsSize = 8;
5170 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5171 gameInfo.holdingsSize = 8;
5172 startedFromSetupPosition = TRUE;
5174 case VariantCrazyhouse:
5175 case VariantBughouse:
5177 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5178 gameInfo.holdingsSize = 5;
5180 case VariantWildCastle:
5182 /* !!?shuffle with kings guaranteed to be on d or e file */
5183 shuffleOpenings = 1;
5185 case VariantNoCastle:
5187 nrCastlingRights = 0;
5188 /* !!?unconstrained back-rank shuffle */
5189 shuffleOpenings = 1;
5194 if(appData.NrFiles >= 0) {
5195 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5196 gameInfo.boardWidth = appData.NrFiles;
5198 if(appData.NrRanks >= 0) {
5199 gameInfo.boardHeight = appData.NrRanks;
5201 if(appData.holdingsSize >= 0) {
5202 i = appData.holdingsSize;
5203 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5204 gameInfo.holdingsSize = i;
5206 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5207 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5208 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5210 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5211 if(pawnRow < 1) pawnRow = 1;
5212 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5214 /* User pieceToChar list overrules defaults */
5215 if(appData.pieceToCharTable != NULL)
5216 SetCharTable(pieceToChar, appData.pieceToCharTable);
5218 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5220 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5221 s = (ChessSquare) 0; /* account holding counts in guard band */
5222 for( i=0; i<BOARD_HEIGHT; i++ )
5223 initialPosition[i][j] = s;
5225 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5226 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5227 initialPosition[pawnRow][j] = WhitePawn;
5228 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5229 if(gameInfo.variant == VariantXiangqi) {
5231 initialPosition[pawnRow][j] =
5232 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5233 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5234 initialPosition[2][j] = WhiteCannon;
5235 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5239 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5241 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5244 initialPosition[1][j] = WhiteBishop;
5245 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5247 initialPosition[1][j] = WhiteRook;
5248 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5251 if( nrCastlingRights == -1) {
5252 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5253 /* This sets default castling rights from none to normal corners */
5254 /* Variants with other castling rights must set them themselves above */
5255 nrCastlingRights = 6;
5257 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5258 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5259 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5260 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5261 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5262 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5265 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5266 if(gameInfo.variant == VariantGreat) { // promotion commoners
5267 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5268 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5269 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5270 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5272 if (appData.debugMode) {
5273 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5275 if(shuffleOpenings) {
5276 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5277 startedFromSetupPosition = TRUE;
5279 if(startedFromPositionFile) {
5280 /* [HGM] loadPos: use PositionFile for every new game */
5281 CopyBoard(initialPosition, filePosition);
5282 for(i=0; i<nrCastlingRights; i++)
5283 initialRights[i] = filePosition[CASTLING][i];
5284 startedFromSetupPosition = TRUE;
5287 CopyBoard(boards[0], initialPosition);
5289 if(oldx != gameInfo.boardWidth ||
5290 oldy != gameInfo.boardHeight ||
5291 oldh != gameInfo.holdingsWidth
5293 || oldv == VariantGothic || // For licensing popups
5294 gameInfo.variant == VariantGothic
5297 || oldv == VariantFalcon ||
5298 gameInfo.variant == VariantFalcon
5301 InitDrawingSizes(-2 ,0);
5304 DrawPosition(TRUE, boards[currentMove]);
5308 SendBoard(cps, moveNum)
5309 ChessProgramState *cps;
5312 char message[MSG_SIZ];
5314 if (cps->useSetboard) {
5315 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5316 sprintf(message, "setboard %s\n", fen);
5317 SendToProgram(message, cps);
5323 /* Kludge to set black to move, avoiding the troublesome and now
5324 * deprecated "black" command.
5326 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5328 SendToProgram("edit\n", cps);
5329 SendToProgram("#\n", cps);
5330 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5331 bp = &boards[moveNum][i][BOARD_LEFT];
5332 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5333 if ((int) *bp < (int) BlackPawn) {
5334 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5336 if(message[0] == '+' || message[0] == '~') {
5337 sprintf(message, "%c%c%c+\n",
5338 PieceToChar((ChessSquare)(DEMOTED *bp)),
5341 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5342 message[1] = BOARD_RGHT - 1 - j + '1';
5343 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5345 SendToProgram(message, cps);
5350 SendToProgram("c\n", cps);
5351 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5352 bp = &boards[moveNum][i][BOARD_LEFT];
5353 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5354 if (((int) *bp != (int) EmptySquare)
5355 && ((int) *bp >= (int) BlackPawn)) {
5356 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5358 if(message[0] == '+' || message[0] == '~') {
5359 sprintf(message, "%c%c%c+\n",
5360 PieceToChar((ChessSquare)(DEMOTED *bp)),
5363 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5364 message[1] = BOARD_RGHT - 1 - j + '1';
5365 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5367 SendToProgram(message, cps);
5372 SendToProgram(".\n", cps);
5374 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5377 static int autoQueen; // [HGM] oneclick
5380 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5382 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5383 /* [HGM] add Shogi promotions */
5384 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5389 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5390 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5392 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5393 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5396 piece = boards[currentMove][fromY][fromX];
5397 if(gameInfo.variant == VariantShogi) {
5398 promotionZoneSize = 3;
5399 highestPromotingPiece = (int)WhiteFerz;
5400 } else if(gameInfo.variant == VariantMakruk) {
5401 promotionZoneSize = 3;
5404 // next weed out all moves that do not touch the promotion zone at all
5405 if((int)piece >= BlackPawn) {
5406 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5408 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5410 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5411 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5414 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5416 // weed out mandatory Shogi promotions
5417 if(gameInfo.variant == VariantShogi) {
5418 if(piece >= BlackPawn) {
5419 if(toY == 0 && piece == BlackPawn ||
5420 toY == 0 && piece == BlackQueen ||
5421 toY <= 1 && piece == BlackKnight) {
5426 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5427 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5428 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5435 // weed out obviously illegal Pawn moves
5436 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5437 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5438 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5439 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5440 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5441 // note we are not allowed to test for valid (non-)capture, due to premove
5444 // we either have a choice what to promote to, or (in Shogi) whether to promote
5445 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5446 *promoChoice = PieceToChar(BlackFerz); // no choice
5449 if(autoQueen) { // predetermined
5450 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5451 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5452 else *promoChoice = PieceToChar(BlackQueen);
5456 // suppress promotion popup on illegal moves that are not premoves
5457 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5458 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5459 if(appData.testLegality && !premove) {
5460 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5461 fromY, fromX, toY, toX, NULLCHAR);
5462 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5463 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5471 InPalace(row, column)
5473 { /* [HGM] for Xiangqi */
5474 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5475 column < (BOARD_WIDTH + 4)/2 &&
5476 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5481 PieceForSquare (x, y)
5485 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5488 return boards[currentMove][y][x];
5492 OKToStartUserMove(x, y)
5495 ChessSquare from_piece;
5498 if (matchMode) return FALSE;
5499 if (gameMode == EditPosition) return TRUE;
5501 if (x >= 0 && y >= 0)
5502 from_piece = boards[currentMove][y][x];
5504 from_piece = EmptySquare;
5506 if (from_piece == EmptySquare) return FALSE;
5508 white_piece = (int)from_piece >= (int)WhitePawn &&
5509 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5512 case PlayFromGameFile:
5514 case TwoMachinesPlay:
5522 case MachinePlaysWhite:
5523 case IcsPlayingBlack:
5524 if (appData.zippyPlay) return FALSE;
5526 DisplayMoveError(_("You are playing Black"));
5531 case MachinePlaysBlack:
5532 case IcsPlayingWhite:
5533 if (appData.zippyPlay) return FALSE;
5535 DisplayMoveError(_("You are playing White"));
5541 if (!white_piece && WhiteOnMove(currentMove)) {
5542 DisplayMoveError(_("It is White's turn"));
5545 if (white_piece && !WhiteOnMove(currentMove)) {
5546 DisplayMoveError(_("It is Black's turn"));
5549 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5550 /* Editing correspondence game history */
5551 /* Could disallow this or prompt for confirmation */
5556 case BeginningOfGame:
5557 if (appData.icsActive) return FALSE;
5558 if (!appData.noChessProgram) {
5560 DisplayMoveError(_("You are playing White"));
5567 if (!white_piece && WhiteOnMove(currentMove)) {
5568 DisplayMoveError(_("It is White's turn"));
5571 if (white_piece && !WhiteOnMove(currentMove)) {
5572 DisplayMoveError(_("It is Black's turn"));
5581 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5582 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5583 && gameMode != AnalyzeFile && gameMode != Training) {
5584 DisplayMoveError(_("Displayed position is not current"));
5591 OnlyMove(int *x, int *y, Boolean captures) {
5592 DisambiguateClosure cl;
5593 if (appData.zippyPlay) return FALSE;
5595 case MachinePlaysBlack:
5596 case IcsPlayingWhite:
5597 case BeginningOfGame:
5598 if(!WhiteOnMove(currentMove)) return FALSE;
5600 case MachinePlaysWhite:
5601 case IcsPlayingBlack:
5602 if(WhiteOnMove(currentMove)) return FALSE;
5607 cl.pieceIn = EmptySquare;
5612 cl.promoCharIn = NULLCHAR;
5613 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5614 if( cl.kind == NormalMove ||
5615 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5616 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5617 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5618 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5625 if(cl.kind != ImpossibleMove) return FALSE;
5626 cl.pieceIn = EmptySquare;
5631 cl.promoCharIn = NULLCHAR;
5632 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5633 if( cl.kind == NormalMove ||
5634 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5635 cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5636 cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5637 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5642 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5648 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5649 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5650 int lastLoadGameUseList = FALSE;
5651 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5652 ChessMove lastLoadGameStart = (ChessMove) 0;
5655 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5656 int fromX, fromY, toX, toY;
5661 ChessSquare pdown, pup;
5663 /* Check if the user is playing in turn. This is complicated because we
5664 let the user "pick up" a piece before it is his turn. So the piece he
5665 tried to pick up may have been captured by the time he puts it down!
5666 Therefore we use the color the user is supposed to be playing in this
5667 test, not the color of the piece that is currently on the starting
5668 square---except in EditGame mode, where the user is playing both
5669 sides; fortunately there the capture race can't happen. (It can
5670 now happen in IcsExamining mode, but that's just too bad. The user
5671 will get a somewhat confusing message in that case.)
5675 case PlayFromGameFile:
5677 case TwoMachinesPlay:
5681 /* We switched into a game mode where moves are not accepted,
5682 perhaps while the mouse button was down. */
5683 return ImpossibleMove;
5685 case MachinePlaysWhite:
5686 /* User is moving for Black */
5687 if (WhiteOnMove(currentMove)) {
5688 DisplayMoveError(_("It is White's turn"));
5689 return ImpossibleMove;
5693 case MachinePlaysBlack:
5694 /* User is moving for White */
5695 if (!WhiteOnMove(currentMove)) {
5696 DisplayMoveError(_("It is Black's turn"));
5697 return ImpossibleMove;
5703 case BeginningOfGame:
5706 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5707 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5708 /* User is moving for Black */
5709 if (WhiteOnMove(currentMove)) {
5710 DisplayMoveError(_("It is White's turn"));
5711 return ImpossibleMove;
5714 /* User is moving for White */
5715 if (!WhiteOnMove(currentMove)) {
5716 DisplayMoveError(_("It is Black's turn"));
5717 return ImpossibleMove;
5722 case IcsPlayingBlack:
5723 /* User is moving for Black */
5724 if (WhiteOnMove(currentMove)) {
5725 if (!appData.premove) {
5726 DisplayMoveError(_("It is White's turn"));
5727 } else if (toX >= 0 && toY >= 0) {
5730 premoveFromX = fromX;
5731 premoveFromY = fromY;
5732 premovePromoChar = promoChar;
5734 if (appData.debugMode)
5735 fprintf(debugFP, "Got premove: fromX %d,"
5736 "fromY %d, toX %d, toY %d\n",
5737 fromX, fromY, toX, toY);
5739 return ImpossibleMove;
5743 case IcsPlayingWhite:
5744 /* User is moving for White */
5745 if (!WhiteOnMove(currentMove)) {
5746 if (!appData.premove) {
5747 DisplayMoveError(_("It is Black's turn"));
5748 } else if (toX >= 0 && toY >= 0) {
5751 premoveFromX = fromX;
5752 premoveFromY = fromY;
5753 premovePromoChar = promoChar;
5755 if (appData.debugMode)
5756 fprintf(debugFP, "Got premove: fromX %d,"
5757 "fromY %d, toX %d, toY %d\n",
5758 fromX, fromY, toX, toY);
5760 return ImpossibleMove;
5768 /* EditPosition, empty square, or different color piece;
5769 click-click move is possible */
5770 if (toX == -2 || toY == -2) {
5771 boards[0][fromY][fromX] = EmptySquare;
5772 return AmbiguousMove;
5773 } else if (toX >= 0 && toY >= 0) {
5774 boards[0][toY][toX] = boards[0][fromY][fromX];
5775 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5776 if(boards[0][fromY][0] != EmptySquare) {
5777 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5778 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5781 if(fromX == BOARD_RGHT+1) {
5782 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5783 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5784 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5787 boards[0][fromY][fromX] = EmptySquare;
5788 return AmbiguousMove;
5790 return ImpossibleMove;
5793 if(toX < 0 || toY < 0) return ImpossibleMove;
5794 pdown = boards[currentMove][fromY][fromX];
5795 pup = boards[currentMove][toY][toX];
5797 /* [HGM] If move started in holdings, it means a drop */
5798 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5799 if( pup != EmptySquare ) return ImpossibleMove;
5800 if(appData.testLegality) {
5801 /* it would be more logical if LegalityTest() also figured out
5802 * which drops are legal. For now we forbid pawns on back rank.
5803 * Shogi is on its own here...
5805 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5806 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5807 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5809 return WhiteDrop; /* Not needed to specify white or black yet */
5812 /* [HGM] always test for legality, to get promotion info */
5813 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5814 fromY, fromX, toY, toX, promoChar);
5815 /* [HGM] but possibly ignore an IllegalMove result */
5816 if (appData.testLegality) {
5817 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5818 DisplayMoveError(_("Illegal move"));
5819 return ImpossibleMove;
5824 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5825 function is made into one that returns an OK move type if FinishMove
5826 should be called. This to give the calling driver routine the
5827 opportunity to finish the userMove input with a promotion popup,
5828 without bothering the user with this for invalid or illegal moves */
5830 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5833 /* Common tail of UserMoveEvent and DropMenuEvent */
5835 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5837 int fromX, fromY, toX, toY;
5838 /*char*/int promoChar;
5842 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5843 // [HGM] superchess: suppress promotions to non-available piece
5844 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5845 if(WhiteOnMove(currentMove)) {
5846 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5848 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5852 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5853 move type in caller when we know the move is a legal promotion */
5854 if(moveType == NormalMove && promoChar)
5855 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5857 /* [HGM] convert drag-and-drop piece drops to standard form */
5858 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5859 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5860 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5861 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5862 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5863 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5864 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5865 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5869 /* [HGM] <popupFix> The following if has been moved here from
5870 UserMoveEvent(). Because it seemed to belong here (why not allow
5871 piece drops in training games?), and because it can only be
5872 performed after it is known to what we promote. */
5873 if (gameMode == Training) {
5874 /* compare the move played on the board to the next move in the
5875 * game. If they match, display the move and the opponent's response.
5876 * If they don't match, display an error message.
5880 CopyBoard(testBoard, boards[currentMove]);
5881 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5883 if (CompareBoards(testBoard, boards[currentMove+1])) {
5884 ForwardInner(currentMove+1);
5886 /* Autoplay the opponent's response.
5887 * if appData.animate was TRUE when Training mode was entered,
5888 * the response will be animated.
5890 saveAnimate = appData.animate;
5891 appData.animate = animateTraining;
5892 ForwardInner(currentMove+1);
5893 appData.animate = saveAnimate;
5895 /* check for the end of the game */
5896 if (currentMove >= forwardMostMove) {
5897 gameMode = PlayFromGameFile;
5899 SetTrainingModeOff();
5900 DisplayInformation(_("End of game"));
5903 DisplayError(_("Incorrect move"), 0);
5908 /* Ok, now we know that the move is good, so we can kill
5909 the previous line in Analysis Mode */
5910 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5911 && currentMove < forwardMostMove) {
5912 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5915 /* If we need the chess program but it's dead, restart it */
5916 ResurrectChessProgram();
5918 /* A user move restarts a paused game*/
5922 thinkOutput[0] = NULLCHAR;
5924 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5926 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5928 if (gameMode == BeginningOfGame) {
5929 if (appData.noChessProgram) {
5930 gameMode = EditGame;
5934 gameMode = MachinePlaysBlack;
5937 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5939 if (first.sendName) {
5940 sprintf(buf, "name %s\n", gameInfo.white);
5941 SendToProgram(buf, &first);
5948 /* Relay move to ICS or chess engine */
5949 if (appData.icsActive) {
5950 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5951 gameMode == IcsExamining) {
5952 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5953 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5955 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5957 // also send plain move, in case ICS does not understand atomic claims
5958 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5962 if (first.sendTime && (gameMode == BeginningOfGame ||
5963 gameMode == MachinePlaysWhite ||
5964 gameMode == MachinePlaysBlack)) {
5965 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5967 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5968 // [HGM] book: if program might be playing, let it use book
5969 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5970 first.maybeThinking = TRUE;
5971 } else SendMoveToProgram(forwardMostMove-1, &first);
5972 if (currentMove == cmailOldMove + 1) {
5973 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5977 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5981 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5987 if (WhiteOnMove(currentMove)) {
5988 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5990 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5994 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5999 case MachinePlaysBlack:
6000 case MachinePlaysWhite:
6001 /* disable certain menu options while machine is thinking */
6002 SetMachineThinkingEnables();
6009 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6011 if(bookHit) { // [HGM] book: simulate book reply
6012 static char bookMove[MSG_SIZ]; // a bit generous?
6014 programStats.nodes = programStats.depth = programStats.time =
6015 programStats.score = programStats.got_only_move = 0;
6016 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6018 strcpy(bookMove, "move ");
6019 strcat(bookMove, bookHit);
6020 HandleMachineMove(bookMove, &first);
6026 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6027 int fromX, fromY, toX, toY;
6030 /* [HGM] This routine was added to allow calling of its two logical
6031 parts from other modules in the old way. Before, UserMoveEvent()
6032 automatically called FinishMove() if the move was OK, and returned
6033 otherwise. I separated the two, in order to make it possible to
6034 slip a promotion popup in between. But that it always needs two
6035 calls, to the first part, (now called UserMoveTest() ), and to
6036 FinishMove if the first part succeeded. Calls that do not need
6037 to do anything in between, can call this routine the old way.
6039 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6040 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6041 if(moveType == AmbiguousMove)
6042 DrawPosition(FALSE, boards[currentMove]);
6043 else if(moveType != ImpossibleMove && moveType != Comment)
6044 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6048 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6055 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6056 Markers *m = (Markers *) closure;
6057 if(rf == fromY && ff == fromX)
6058 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6059 || kind == WhiteCapturesEnPassant
6060 || kind == BlackCapturesEnPassant);
6061 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6065 MarkTargetSquares(int clear)
6068 if(!appData.markers || !appData.highlightDragging ||
6069 !appData.testLegality || gameMode == EditPosition) return;
6071 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6074 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6075 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6076 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6078 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6081 DrawPosition(TRUE, NULL);
6084 void LeftClick(ClickType clickType, int xPix, int yPix)
6087 Boolean saveAnimate;
6088 static int second = 0, promotionChoice = 0;
6089 char promoChoice = NULLCHAR;
6091 if(appData.seekGraph && appData.icsActive && loggedOn &&
6092 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6093 SeekGraphClick(clickType, xPix, yPix, 0);
6097 if (clickType == Press) ErrorPopDown();
6098 MarkTargetSquares(1);
6100 x = EventToSquare(xPix, BOARD_WIDTH);
6101 y = EventToSquare(yPix, BOARD_HEIGHT);
6102 if (!flipView && y >= 0) {
6103 y = BOARD_HEIGHT - 1 - y;
6105 if (flipView && x >= 0) {
6106 x = BOARD_WIDTH - 1 - x;
6109 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6110 if(clickType == Release) return; // ignore upclick of click-click destination
6111 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6112 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6113 if(gameInfo.holdingsWidth &&
6114 (WhiteOnMove(currentMove)
6115 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6116 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6117 // click in right holdings, for determining promotion piece
6118 ChessSquare p = boards[currentMove][y][x];
6119 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6120 if(p != EmptySquare) {
6121 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6126 DrawPosition(FALSE, boards[currentMove]);
6130 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6131 if(clickType == Press
6132 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6133 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6134 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6137 autoQueen = appData.alwaysPromoteToQueen;
6140 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6141 if (clickType == Press) {
6143 if (OKToStartUserMove(x, y)) {
6147 MarkTargetSquares(0);
6148 DragPieceBegin(xPix, yPix);
6149 if (appData.highlightDragging) {
6150 SetHighlights(x, y, -1, -1);
6159 if (clickType == Press && gameMode != EditPosition) {
6164 // ignore off-board to clicks
6165 if(y < 0 || x < 0) return;
6167 /* Check if clicking again on the same color piece */
6168 fromP = boards[currentMove][fromY][fromX];
6169 toP = boards[currentMove][y][x];
6170 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6171 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6172 WhitePawn <= toP && toP <= WhiteKing &&
6173 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6174 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6175 (BlackPawn <= fromP && fromP <= BlackKing &&
6176 BlackPawn <= toP && toP <= BlackKing &&
6177 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6178 !(fromP == BlackKing && toP == BlackRook && frc))) {
6179 /* Clicked again on same color piece -- changed his mind */
6180 second = (x == fromX && y == fromY);
6181 if(!second || !OnlyMove(&x, &y, TRUE)) {
6182 if (appData.highlightDragging) {
6183 SetHighlights(x, y, -1, -1);
6187 if (OKToStartUserMove(x, y)) {
6190 MarkTargetSquares(0);
6191 DragPieceBegin(xPix, yPix);
6196 // ignore clicks on holdings
6197 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6200 if (clickType == Release && x == fromX && y == fromY) {
6201 DragPieceEnd(xPix, yPix);
6202 if (appData.animateDragging) {
6203 /* Undo animation damage if any */
6204 DrawPosition(FALSE, NULL);
6207 /* Second up/down in same square; just abort move */
6212 ClearPremoveHighlights();
6214 /* First upclick in same square; start click-click mode */
6215 SetHighlights(x, y, -1, -1);
6220 /* we now have a different from- and (possibly off-board) to-square */
6221 /* Completed move */
6224 saveAnimate = appData.animate;
6225 if (clickType == Press) {
6226 /* Finish clickclick move */
6227 if (appData.animate || appData.highlightLastMove) {
6228 SetHighlights(fromX, fromY, toX, toY);
6233 /* Finish drag move */
6234 if (appData.highlightLastMove) {
6235 SetHighlights(fromX, fromY, toX, toY);
6239 DragPieceEnd(xPix, yPix);
6240 /* Don't animate move and drag both */
6241 appData.animate = FALSE;
6244 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6245 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6246 ChessSquare piece = boards[currentMove][fromY][fromX];
6247 if(gameMode == EditPosition && piece != EmptySquare &&
6248 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6251 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6252 n = PieceToNumber(piece - (int)BlackPawn);
6253 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6254 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6255 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6257 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6258 n = PieceToNumber(piece);
6259 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6260 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6261 boards[currentMove][n][BOARD_WIDTH-2]++;
6263 boards[currentMove][fromY][fromX] = EmptySquare;
6267 DrawPosition(TRUE, boards[currentMove]);
6271 // off-board moves should not be highlighted
6272 if(x < 0 || x < 0) ClearHighlights();
6274 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6275 SetHighlights(fromX, fromY, toX, toY);
6276 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6277 // [HGM] super: promotion to captured piece selected from holdings
6278 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6279 promotionChoice = TRUE;
6280 // kludge follows to temporarily execute move on display, without promoting yet
6281 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6282 boards[currentMove][toY][toX] = p;
6283 DrawPosition(FALSE, boards[currentMove]);
6284 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6285 boards[currentMove][toY][toX] = q;
6286 DisplayMessage("Click in holdings to choose piece", "");
6291 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6292 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6293 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6296 appData.animate = saveAnimate;
6297 if (appData.animate || appData.animateDragging) {
6298 /* Undo animation damage if needed */
6299 DrawPosition(FALSE, NULL);
6303 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6304 { // front-end-free part taken out of PieceMenuPopup
6305 int whichMenu; int xSqr, ySqr;
6307 if(seekGraphUp) { // [HGM] seekgraph
6308 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6309 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6313 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6314 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6315 if(action == Press) { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6316 if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6320 xSqr = EventToSquare(x, BOARD_WIDTH);
6321 ySqr = EventToSquare(y, BOARD_HEIGHT);
6322 if (action == Release) UnLoadPV(); // [HGM] pv
6323 if (action != Press) return -2; // return code to be ignored
6326 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6328 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6329 if (xSqr < 0 || ySqr < 0) return -1;
\r
6330 whichMenu = 0; // edit-position menu
6333 if(!appData.icsEngineAnalyze) return -1;
6334 case IcsPlayingWhite:
6335 case IcsPlayingBlack:
6336 if(!appData.zippyPlay) goto noZip;
6339 case MachinePlaysWhite:
6340 case MachinePlaysBlack:
6341 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6342 if (!appData.dropMenu) {
6344 return 2; // flag front-end to grab mouse events
6346 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6347 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6350 if (xSqr < 0 || ySqr < 0) return -1;
6351 if (!appData.dropMenu || appData.testLegality &&
6352 gameInfo.variant != VariantBughouse &&
6353 gameInfo.variant != VariantCrazyhouse) return -1;
6354 whichMenu = 1; // drop menu
6360 if (((*fromX = xSqr) < 0) ||
6361 ((*fromY = ySqr) < 0)) {
6362 *fromX = *fromY = -1;
6366 *fromX = BOARD_WIDTH - 1 - *fromX;
6368 *fromY = BOARD_HEIGHT - 1 - *fromY;
6373 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6375 // char * hint = lastHint;
6376 FrontEndProgramStats stats;
6378 stats.which = cps == &first ? 0 : 1;
6379 stats.depth = cpstats->depth;
6380 stats.nodes = cpstats->nodes;
6381 stats.score = cpstats->score;
6382 stats.time = cpstats->time;
6383 stats.pv = cpstats->movelist;
6384 stats.hint = lastHint;
6385 stats.an_move_index = 0;
6386 stats.an_move_count = 0;
6388 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6389 stats.hint = cpstats->move_name;
6390 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6391 stats.an_move_count = cpstats->nr_moves;
6394 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6396 SetProgramStats( &stats );
6400 Adjudicate(ChessProgramState *cps)
6401 { // [HGM] some adjudications useful with buggy engines
6402 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6403 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6404 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6405 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6406 int k, count = 0; static int bare = 1;
6407 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6408 Boolean canAdjudicate = !appData.icsActive;
6410 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6411 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6412 if( appData.testLegality )
6413 { /* [HGM] Some more adjudications for obstinate engines */
6414 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6415 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6416 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6417 static int moveCount = 6;
6419 char *reason = NULL;
6421 /* Count what is on board. */
6422 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6423 { ChessSquare p = boards[forwardMostMove][i][j];
6427 { /* count B,N,R and other of each side */
6430 NrK++; break; // [HGM] atomic: count Kings
6434 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6435 bishopsColor |= 1 << ((i^j)&1);
6440 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6441 bishopsColor |= 1 << ((i^j)&1);
6456 PawnAdvance += m; NrPawns++;
6458 NrPieces += (p != EmptySquare);
6459 NrW += ((int)p < (int)BlackPawn);
6460 if(gameInfo.variant == VariantXiangqi &&
6461 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6462 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6463 NrW -= ((int)p < (int)BlackPawn);
6467 /* Some material-based adjudications that have to be made before stalemate test */
6468 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6469 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6470 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6471 if(canAdjudicate && appData.checkMates) {
6473 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6474 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6475 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6476 "Xboard adjudication: King destroyed", GE_XBOARD );
6481 /* Bare King in Shatranj (loses) or Losers (wins) */
6482 if( NrW == 1 || NrPieces - NrW == 1) {
6483 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6484 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6485 if(canAdjudicate && appData.checkMates) {
6487 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6488 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6489 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6490 "Xboard adjudication: Bare king", GE_XBOARD );
6494 if( gameInfo.variant == VariantShatranj && --bare < 0)
6496 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6497 if(canAdjudicate && appData.checkMates) {
6498 /* but only adjudicate if adjudication enabled */
6500 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6501 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6502 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6503 "Xboard adjudication: Bare king", GE_XBOARD );
6510 // don't wait for engine to announce game end if we can judge ourselves
6511 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6513 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6514 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6515 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6516 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6519 reason = "Xboard adjudication: 3rd check";
6520 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6530 reason = "Xboard adjudication: Stalemate";
6531 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6532 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6533 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6534 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6535 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6536 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6537 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6538 EP_CHECKMATE : EP_WINS);
6539 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6540 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6544 reason = "Xboard adjudication: Checkmate";
6545 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6549 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6551 result = GameIsDrawn; break;
6553 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6555 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6557 result = (ChessMove) 0;
6559 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6561 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6562 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563 GameEnds( result, reason, GE_XBOARD );
6567 /* Next absolutely insufficient mating material. */
6568 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6569 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6570 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6571 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6572 { /* KBK, KNK, KK of KBKB with like Bishops */
6574 /* always flag draws, for judging claims */
6575 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6577 if(canAdjudicate && appData.materialDraws) {
6578 /* but only adjudicate them if adjudication enabled */
6579 if(engineOpponent) {
6580 SendToProgram("force\n", engineOpponent); // suppress reply
6581 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6583 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6584 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6589 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6591 ( NrWR == 1 && NrBR == 1 /* KRKR */
6592 || NrWQ==1 && NrBQ==1 /* KQKQ */
6593 || NrWN==2 || NrBN==2 /* KNNK */
6594 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6596 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6597 { /* if the first 3 moves do not show a tactical win, declare draw */
6598 if(engineOpponent) {
6599 SendToProgram("force\n", engineOpponent); // suppress reply
6600 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6602 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6606 } else moveCount = 6;
6610 if (appData.debugMode) { int i;
6611 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6612 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6613 appData.drawRepeats);
6614 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6615 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6619 // Repetition draws and 50-move rule can be applied independently of legality testing
6621 /* Check for rep-draws */
6623 for(k = forwardMostMove-2;
6624 k>=backwardMostMove && k>=forwardMostMove-100 &&
6625 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6626 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6629 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6630 /* compare castling rights */
6631 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6632 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6633 rights++; /* King lost rights, while rook still had them */
6634 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6635 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6636 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6637 rights++; /* but at least one rook lost them */
6639 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6640 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6642 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6643 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6644 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6647 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6648 && appData.drawRepeats > 1) {
6649 /* adjudicate after user-specified nr of repeats */
6650 if(engineOpponent) {
6651 SendToProgram("force\n", engineOpponent); // suppress reply
6652 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6654 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6656 // [HGM] xiangqi: check for forbidden perpetuals
6657 int m, ourPerpetual = 1, hisPerpetual = 1;
6658 for(m=forwardMostMove; m>k; m-=2) {
6659 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6660 ourPerpetual = 0; // the current mover did not always check
6661 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6662 hisPerpetual = 0; // the opponent did not always check
6664 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6665 ourPerpetual, hisPerpetual);
6666 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6667 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6668 "Xboard adjudication: perpetual checking", GE_XBOARD );
6671 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6672 break; // (or we would have caught him before). Abort repetition-checking loop.
6673 // Now check for perpetual chases
6674 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6675 hisPerpetual = PerpetualChase(k, forwardMostMove);
6676 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6677 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6678 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6679 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6682 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6683 break; // Abort repetition-checking loop.
6685 // if neither of us is checking or chasing all the time, or both are, it is draw
6687 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6690 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6691 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6695 /* Now we test for 50-move draws. Determine ply count */
6696 count = forwardMostMove;
6697 /* look for last irreversble move */
6698 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6700 /* if we hit starting position, add initial plies */
6701 if( count == backwardMostMove )
6702 count -= initialRulePlies;
6703 count = forwardMostMove - count;
6705 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6706 /* this is used to judge if draw claims are legal */
6707 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6708 if(engineOpponent) {
6709 SendToProgram("force\n", engineOpponent); // suppress reply
6710 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6712 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6713 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6717 /* if draw offer is pending, treat it as a draw claim
6718 * when draw condition present, to allow engines a way to
6719 * claim draws before making their move to avoid a race
6720 * condition occurring after their move
6722 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6724 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6725 p = "Draw claim: 50-move rule";
6726 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6727 p = "Draw claim: 3-fold repetition";
6728 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6729 p = "Draw claim: insufficient mating material";
6730 if( p != NULL && canAdjudicate) {
6731 if(engineOpponent) {
6732 SendToProgram("force\n", engineOpponent); // suppress reply
6733 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6735 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6736 GameEnds( GameIsDrawn, p, GE_XBOARD );
6741 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6742 if(engineOpponent) {
6743 SendToProgram("force\n", engineOpponent); // suppress reply
6744 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6746 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6747 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6753 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6754 { // [HGM] book: this routine intercepts moves to simulate book replies
6755 char *bookHit = NULL;
6757 //first determine if the incoming move brings opponent into his book
6758 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6759 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6760 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6761 if(bookHit != NULL && !cps->bookSuspend) {
6762 // make sure opponent is not going to reply after receiving move to book position
6763 SendToProgram("force\n", cps);
6764 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6766 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6767 // now arrange restart after book miss
6769 // after a book hit we never send 'go', and the code after the call to this routine
6770 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6772 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6773 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6774 SendToProgram(buf, cps);
6775 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6776 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6777 SendToProgram("go\n", cps);
6778 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6779 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6780 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6781 SendToProgram("go\n", cps);
6782 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6784 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6788 ChessProgramState *savedState;
6789 void DeferredBookMove(void)
6791 if(savedState->lastPing != savedState->lastPong)
6792 ScheduleDelayedEvent(DeferredBookMove, 10);
6794 HandleMachineMove(savedMessage, savedState);
6798 HandleMachineMove(message, cps)
6800 ChessProgramState *cps;
6802 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6803 char realname[MSG_SIZ];
6804 int fromX, fromY, toX, toY;
6813 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6815 * Kludge to ignore BEL characters
6817 while (*message == '\007') message++;
6820 * [HGM] engine debug message: ignore lines starting with '#' character
6822 if(cps->debug && *message == '#') return;
6825 * Look for book output
6827 if (cps == &first && bookRequested) {
6828 if (message[0] == '\t' || message[0] == ' ') {
6829 /* Part of the book output is here; append it */
6830 strcat(bookOutput, message);
6831 strcat(bookOutput, " \n");
6833 } else if (bookOutput[0] != NULLCHAR) {
6834 /* All of book output has arrived; display it */
6835 char *p = bookOutput;
6836 while (*p != NULLCHAR) {
6837 if (*p == '\t') *p = ' ';
6840 DisplayInformation(bookOutput);
6841 bookRequested = FALSE;
6842 /* Fall through to parse the current output */
6847 * Look for machine move.
6849 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6850 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6852 /* This method is only useful on engines that support ping */
6853 if (cps->lastPing != cps->lastPong) {
6854 if (gameMode == BeginningOfGame) {
6855 /* Extra move from before last new; ignore */
6856 if (appData.debugMode) {
6857 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6860 if (appData.debugMode) {
6861 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6862 cps->which, gameMode);
6865 SendToProgram("undo\n", cps);
6871 case BeginningOfGame:
6872 /* Extra move from before last reset; ignore */
6873 if (appData.debugMode) {
6874 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6881 /* Extra move after we tried to stop. The mode test is
6882 not a reliable way of detecting this problem, but it's
6883 the best we can do on engines that don't support ping.
6885 if (appData.debugMode) {
6886 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6887 cps->which, gameMode);
6889 SendToProgram("undo\n", cps);
6892 case MachinePlaysWhite:
6893 case IcsPlayingWhite:
6894 machineWhite = TRUE;
6897 case MachinePlaysBlack:
6898 case IcsPlayingBlack:
6899 machineWhite = FALSE;
6902 case TwoMachinesPlay:
6903 machineWhite = (cps->twoMachinesColor[0] == 'w');
6906 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6907 if (appData.debugMode) {
6909 "Ignoring move out of turn by %s, gameMode %d"
6910 ", forwardMost %d\n",
6911 cps->which, gameMode, forwardMostMove);
6916 if (appData.debugMode) { int f = forwardMostMove;
6917 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6918 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6919 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6921 if(cps->alphaRank) AlphaRank(machineMove, 4);
6922 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6923 &fromX, &fromY, &toX, &toY, &promoChar)) {
6924 /* Machine move could not be parsed; ignore it. */
6925 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6926 machineMove, cps->which);
6927 DisplayError(buf1, 0);
6928 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6929 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6930 if (gameMode == TwoMachinesPlay) {
6931 GameEnds(machineWhite ? BlackWins : WhiteWins,
6937 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6938 /* So we have to redo legality test with true e.p. status here, */
6939 /* to make sure an illegal e.p. capture does not slip through, */
6940 /* to cause a forfeit on a justified illegal-move complaint */
6941 /* of the opponent. */
6942 if( gameMode==TwoMachinesPlay && appData.testLegality
6943 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6946 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6947 fromY, fromX, toY, toX, promoChar);
6948 if (appData.debugMode) {
6950 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6951 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6952 fprintf(debugFP, "castling rights\n");
6954 if(moveType == IllegalMove) {
6955 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6956 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6957 GameEnds(machineWhite ? BlackWins : WhiteWins,
6960 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6961 /* [HGM] Kludge to handle engines that send FRC-style castling
6962 when they shouldn't (like TSCP-Gothic) */
6964 case WhiteASideCastleFR:
6965 case BlackASideCastleFR:
6967 currentMoveString[2]++;
6969 case WhiteHSideCastleFR:
6970 case BlackHSideCastleFR:
6972 currentMoveString[2]--;
6974 default: ; // nothing to do, but suppresses warning of pedantic compilers
6977 hintRequested = FALSE;
6978 lastHint[0] = NULLCHAR;
6979 bookRequested = FALSE;
6980 /* Program may be pondering now */
6981 cps->maybeThinking = TRUE;
6982 if (cps->sendTime == 2) cps->sendTime = 1;
6983 if (cps->offeredDraw) cps->offeredDraw--;
6985 /* currentMoveString is set as a side-effect of ParseOneMove */
6986 strcpy(machineMove, currentMoveString);
6987 strcat(machineMove, "\n");
6988 strcpy(moveList[forwardMostMove], machineMove);
6990 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6992 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6993 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6996 while( count < adjudicateLossPlies ) {
6997 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7000 score = -score; /* Flip score for winning side */
7003 if( score > adjudicateLossThreshold ) {
7010 if( count >= adjudicateLossPlies ) {
7011 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7013 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7014 "Xboard adjudication",
7021 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7024 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7026 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7027 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7029 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7031 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7033 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7034 char buf[3*MSG_SIZ];
7036 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7037 programStats.score / 100.,
7039 programStats.time / 100.,
7040 (unsigned int)programStats.nodes,
7041 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7042 programStats.movelist);
7044 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7049 /* [AS] Save move info and clear stats for next move */
7050 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7051 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7052 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7053 ClearProgramStats();
7054 thinkOutput[0] = NULLCHAR;
7055 hiddenThinkOutputState = 0;
7058 if (gameMode == TwoMachinesPlay) {
7059 /* [HGM] relaying draw offers moved to after reception of move */
7060 /* and interpreting offer as claim if it brings draw condition */
7061 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7062 SendToProgram("draw\n", cps->other);
7064 if (cps->other->sendTime) {
7065 SendTimeRemaining(cps->other,
7066 cps->other->twoMachinesColor[0] == 'w');
7068 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7069 if (firstMove && !bookHit) {
7071 if (cps->other->useColors) {
7072 SendToProgram(cps->other->twoMachinesColor, cps->other);
7074 SendToProgram("go\n", cps->other);
7076 cps->other->maybeThinking = TRUE;
7079 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081 if (!pausing && appData.ringBellAfterMoves) {
7086 * Reenable menu items that were disabled while
7087 * machine was thinking
7089 if (gameMode != TwoMachinesPlay)
7090 SetUserThinkingEnables();
7092 // [HGM] book: after book hit opponent has received move and is now in force mode
7093 // force the book reply into it, and then fake that it outputted this move by jumping
7094 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7096 static char bookMove[MSG_SIZ]; // a bit generous?
7098 strcpy(bookMove, "move ");
7099 strcat(bookMove, bookHit);
7102 programStats.nodes = programStats.depth = programStats.time =
7103 programStats.score = programStats.got_only_move = 0;
7104 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7106 if(cps->lastPing != cps->lastPong) {
7107 savedMessage = message; // args for deferred call
7109 ScheduleDelayedEvent(DeferredBookMove, 10);
7118 /* Set special modes for chess engines. Later something general
7119 * could be added here; for now there is just one kludge feature,
7120 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7121 * when "xboard" is given as an interactive command.
7123 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7124 cps->useSigint = FALSE;
7125 cps->useSigterm = FALSE;
7127 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7128 ParseFeatures(message+8, cps);
7129 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7132 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7133 * want this, I was asked to put it in, and obliged.
7135 if (!strncmp(message, "setboard ", 9)) {
7136 Board initial_position;
7138 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7140 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7141 DisplayError(_("Bad FEN received from engine"), 0);
7145 CopyBoard(boards[0], initial_position);
7146 initialRulePlies = FENrulePlies;
7147 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7148 else gameMode = MachinePlaysBlack;
7149 DrawPosition(FALSE, boards[currentMove]);
7155 * Look for communication commands
7157 if (!strncmp(message, "telluser ", 9)) {
7158 DisplayNote(message + 9);
7161 if (!strncmp(message, "tellusererror ", 14)) {
7163 DisplayError(message + 14, 0);
7166 if (!strncmp(message, "tellopponent ", 13)) {
7167 if (appData.icsActive) {
7169 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7173 DisplayNote(message + 13);
7177 if (!strncmp(message, "tellothers ", 11)) {
7178 if (appData.icsActive) {
7180 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7186 if (!strncmp(message, "tellall ", 8)) {
7187 if (appData.icsActive) {
7189 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7193 DisplayNote(message + 8);
7197 if (strncmp(message, "warning", 7) == 0) {
7198 /* Undocumented feature, use tellusererror in new code */
7199 DisplayError(message, 0);
7202 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7203 strcpy(realname, cps->tidy);
7204 strcat(realname, " query");
7205 AskQuestion(realname, buf2, buf1, cps->pr);
7208 /* Commands from the engine directly to ICS. We don't allow these to be
7209 * sent until we are logged on. Crafty kibitzes have been known to
7210 * interfere with the login process.
7213 if (!strncmp(message, "tellics ", 8)) {
7214 SendToICS(message + 8);
7218 if (!strncmp(message, "tellicsnoalias ", 15)) {
7219 SendToICS(ics_prefix);
7220 SendToICS(message + 15);
7224 /* The following are for backward compatibility only */
7225 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7226 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7227 SendToICS(ics_prefix);
7233 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7237 * If the move is illegal, cancel it and redraw the board.
7238 * Also deal with other error cases. Matching is rather loose
7239 * here to accommodate engines written before the spec.
7241 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7242 strncmp(message, "Error", 5) == 0) {
7243 if (StrStr(message, "name") ||
7244 StrStr(message, "rating") || StrStr(message, "?") ||
7245 StrStr(message, "result") || StrStr(message, "board") ||
7246 StrStr(message, "bk") || StrStr(message, "computer") ||
7247 StrStr(message, "variant") || StrStr(message, "hint") ||
7248 StrStr(message, "random") || StrStr(message, "depth") ||
7249 StrStr(message, "accepted")) {
7252 if (StrStr(message, "protover")) {
7253 /* Program is responding to input, so it's apparently done
7254 initializing, and this error message indicates it is
7255 protocol version 1. So we don't need to wait any longer
7256 for it to initialize and send feature commands. */
7257 FeatureDone(cps, 1);
7258 cps->protocolVersion = 1;
7261 cps->maybeThinking = FALSE;
7263 if (StrStr(message, "draw")) {
7264 /* Program doesn't have "draw" command */
7265 cps->sendDrawOffers = 0;
7268 if (cps->sendTime != 1 &&
7269 (StrStr(message, "time") || StrStr(message, "otim"))) {
7270 /* Program apparently doesn't have "time" or "otim" command */
7274 if (StrStr(message, "analyze")) {
7275 cps->analysisSupport = FALSE;
7276 cps->analyzing = FALSE;
7278 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7279 DisplayError(buf2, 0);
7282 if (StrStr(message, "(no matching move)st")) {
7283 /* Special kludge for GNU Chess 4 only */
7284 cps->stKludge = TRUE;
7285 SendTimeControl(cps, movesPerSession, timeControl,
7286 timeIncrement, appData.searchDepth,
7290 if (StrStr(message, "(no matching move)sd")) {
7291 /* Special kludge for GNU Chess 4 only */
7292 cps->sdKludge = TRUE;
7293 SendTimeControl(cps, movesPerSession, timeControl,
7294 timeIncrement, appData.searchDepth,
7298 if (!StrStr(message, "llegal")) {
7301 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7302 gameMode == IcsIdle) return;
7303 if (forwardMostMove <= backwardMostMove) return;
7304 if (pausing) PauseEvent();
7305 if(appData.forceIllegal) {
7306 // [HGM] illegal: machine refused move; force position after move into it
7307 SendToProgram("force\n", cps);
7308 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7309 // we have a real problem now, as SendBoard will use the a2a3 kludge
7310 // when black is to move, while there might be nothing on a2 or black
7311 // might already have the move. So send the board as if white has the move.
7312 // But first we must change the stm of the engine, as it refused the last move
7313 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7314 if(WhiteOnMove(forwardMostMove)) {
7315 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7316 SendBoard(cps, forwardMostMove); // kludgeless board
7318 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7319 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7320 SendBoard(cps, forwardMostMove+1); // kludgeless board
7322 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7323 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7324 gameMode == TwoMachinesPlay)
7325 SendToProgram("go\n", cps);
7328 if (gameMode == PlayFromGameFile) {
7329 /* Stop reading this game file */
7330 gameMode = EditGame;
7333 currentMove = forwardMostMove-1;
7334 DisplayMove(currentMove-1); /* before DisplayMoveError */
7335 SwitchClocks(forwardMostMove-1); // [HGM] race
7336 DisplayBothClocks();
7337 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7338 parseList[currentMove], cps->which);
7339 DisplayMoveError(buf1);
7340 DrawPosition(FALSE, boards[currentMove]);
7342 /* [HGM] illegal-move claim should forfeit game when Xboard */
7343 /* only passes fully legal moves */
7344 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7345 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7346 "False illegal-move claim", GE_XBOARD );
7350 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7351 /* Program has a broken "time" command that
7352 outputs a string not ending in newline.
7358 * If chess program startup fails, exit with an error message.
7359 * Attempts to recover here are futile.
7361 if ((StrStr(message, "unknown host") != NULL)
7362 || (StrStr(message, "No remote directory") != NULL)
7363 || (StrStr(message, "not found") != NULL)
7364 || (StrStr(message, "No such file") != NULL)
7365 || (StrStr(message, "can't alloc") != NULL)
7366 || (StrStr(message, "Permission denied") != NULL)) {
7368 cps->maybeThinking = FALSE;
7369 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7370 cps->which, cps->program, cps->host, message);
7371 RemoveInputSource(cps->isr);
7372 DisplayFatalError(buf1, 0, 1);
7377 * Look for hint output
7379 if (sscanf(message, "Hint: %s", buf1) == 1) {
7380 if (cps == &first && hintRequested) {
7381 hintRequested = FALSE;
7382 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7383 &fromX, &fromY, &toX, &toY, &promoChar)) {
7384 (void) CoordsToAlgebraic(boards[forwardMostMove],
7385 PosFlags(forwardMostMove),
7386 fromY, fromX, toY, toX, promoChar, buf1);
7387 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7388 DisplayInformation(buf2);
7390 /* Hint move could not be parsed!? */
7391 snprintf(buf2, sizeof(buf2),
7392 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7394 DisplayError(buf2, 0);
7397 strcpy(lastHint, buf1);
7403 * Ignore other messages if game is not in progress
7405 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7406 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7409 * look for win, lose, draw, or draw offer
7411 if (strncmp(message, "1-0", 3) == 0) {
7412 char *p, *q, *r = "";
7413 p = strchr(message, '{');
7421 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7423 } else if (strncmp(message, "0-1", 3) == 0) {
7424 char *p, *q, *r = "";
7425 p = strchr(message, '{');
7433 /* Kludge for Arasan 4.1 bug */
7434 if (strcmp(r, "Black resigns") == 0) {
7435 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7438 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7440 } else if (strncmp(message, "1/2", 3) == 0) {
7441 char *p, *q, *r = "";
7442 p = strchr(message, '{');
7451 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7454 } else if (strncmp(message, "White resign", 12) == 0) {
7455 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7457 } else if (strncmp(message, "Black resign", 12) == 0) {
7458 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7460 } else if (strncmp(message, "White matches", 13) == 0 ||
7461 strncmp(message, "Black matches", 13) == 0 ) {
7462 /* [HGM] ignore GNUShogi noises */
7464 } else if (strncmp(message, "White", 5) == 0 &&
7465 message[5] != '(' &&
7466 StrStr(message, "Black") == NULL) {
7467 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7469 } else if (strncmp(message, "Black", 5) == 0 &&
7470 message[5] != '(') {
7471 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7473 } else if (strcmp(message, "resign") == 0 ||
7474 strcmp(message, "computer resigns") == 0) {
7476 case MachinePlaysBlack:
7477 case IcsPlayingBlack:
7478 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7480 case MachinePlaysWhite:
7481 case IcsPlayingWhite:
7482 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7484 case TwoMachinesPlay:
7485 if (cps->twoMachinesColor[0] == 'w')
7486 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7488 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7495 } else if (strncmp(message, "opponent mates", 14) == 0) {
7497 case MachinePlaysBlack:
7498 case IcsPlayingBlack:
7499 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7501 case MachinePlaysWhite:
7502 case IcsPlayingWhite:
7503 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7505 case TwoMachinesPlay:
7506 if (cps->twoMachinesColor[0] == 'w')
7507 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7509 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7516 } else if (strncmp(message, "computer mates", 14) == 0) {
7518 case MachinePlaysBlack:
7519 case IcsPlayingBlack:
7520 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7522 case MachinePlaysWhite:
7523 case IcsPlayingWhite:
7524 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7526 case TwoMachinesPlay:
7527 if (cps->twoMachinesColor[0] == 'w')
7528 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7530 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7537 } else if (strncmp(message, "checkmate", 9) == 0) {
7538 if (WhiteOnMove(forwardMostMove)) {
7539 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7541 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7544 } else if (strstr(message, "Draw") != NULL ||
7545 strstr(message, "game is a draw") != NULL) {
7546 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7548 } else if (strstr(message, "offer") != NULL &&
7549 strstr(message, "draw") != NULL) {
7551 if (appData.zippyPlay && first.initDone) {
7552 /* Relay offer to ICS */
7553 SendToICS(ics_prefix);
7554 SendToICS("draw\n");
7557 cps->offeredDraw = 2; /* valid until this engine moves twice */
7558 if (gameMode == TwoMachinesPlay) {
7559 if (cps->other->offeredDraw) {
7560 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7561 /* [HGM] in two-machine mode we delay relaying draw offer */
7562 /* until after we also have move, to see if it is really claim */
7564 } else if (gameMode == MachinePlaysWhite ||
7565 gameMode == MachinePlaysBlack) {
7566 if (userOfferedDraw) {
7567 DisplayInformation(_("Machine accepts your draw offer"));
7568 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7570 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7577 * Look for thinking output
7579 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7580 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7582 int plylev, mvleft, mvtot, curscore, time;
7583 char mvname[MOVE_LEN];
7587 int prefixHint = FALSE;
7588 mvname[0] = NULLCHAR;
7591 case MachinePlaysBlack:
7592 case IcsPlayingBlack:
7593 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7595 case MachinePlaysWhite:
7596 case IcsPlayingWhite:
7597 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7602 case IcsObserving: /* [DM] icsEngineAnalyze */
7603 if (!appData.icsEngineAnalyze) ignore = TRUE;
7605 case TwoMachinesPlay:
7606 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7617 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7618 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7620 if (plyext != ' ' && plyext != '\t') {
7624 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7625 if( cps->scoreIsAbsolute &&
7626 ( gameMode == MachinePlaysBlack ||
7627 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7628 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7629 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7630 !WhiteOnMove(currentMove)
7633 curscore = -curscore;
7637 programStats.depth = plylev;
7638 programStats.nodes = nodes;
7639 programStats.time = time;
7640 programStats.score = curscore;
7641 programStats.got_only_move = 0;
7643 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7646 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7647 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7648 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7649 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7650 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7651 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7652 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7653 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7656 /* Buffer overflow protection */
7657 if (buf1[0] != NULLCHAR) {
7658 if (strlen(buf1) >= sizeof(programStats.movelist)
7659 && appData.debugMode) {
7661 "PV is too long; using the first %u bytes.\n",
7662 (unsigned) sizeof(programStats.movelist) - 1);
7665 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7667 sprintf(programStats.movelist, " no PV\n");
7670 if (programStats.seen_stat) {
7671 programStats.ok_to_send = 1;
7674 if (strchr(programStats.movelist, '(') != NULL) {
7675 programStats.line_is_book = 1;
7676 programStats.nr_moves = 0;
7677 programStats.moves_left = 0;
7679 programStats.line_is_book = 0;
7682 SendProgramStatsToFrontend( cps, &programStats );
7685 [AS] Protect the thinkOutput buffer from overflow... this
7686 is only useful if buf1 hasn't overflowed first!
7688 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7690 (gameMode == TwoMachinesPlay ?
7691 ToUpper(cps->twoMachinesColor[0]) : ' '),
7692 ((double) curscore) / 100.0,
7693 prefixHint ? lastHint : "",
7694 prefixHint ? " " : "" );
7696 if( buf1[0] != NULLCHAR ) {
7697 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7699 if( strlen(buf1) > max_len ) {
7700 if( appData.debugMode) {
7701 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7703 buf1[max_len+1] = '\0';
7706 strcat( thinkOutput, buf1 );
7709 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7710 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7711 DisplayMove(currentMove - 1);
7715 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7716 /* crafty (9.25+) says "(only move) <move>"
7717 * if there is only 1 legal move
7719 sscanf(p, "(only move) %s", buf1);
7720 sprintf(thinkOutput, "%s (only move)", buf1);
7721 sprintf(programStats.movelist, "%s (only move)", buf1);
7722 programStats.depth = 1;
7723 programStats.nr_moves = 1;
7724 programStats.moves_left = 1;
7725 programStats.nodes = 1;
7726 programStats.time = 1;
7727 programStats.got_only_move = 1;
7729 /* Not really, but we also use this member to
7730 mean "line isn't going to change" (Crafty
7731 isn't searching, so stats won't change) */
7732 programStats.line_is_book = 1;
7734 SendProgramStatsToFrontend( cps, &programStats );
7736 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7737 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7738 DisplayMove(currentMove - 1);
7741 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7742 &time, &nodes, &plylev, &mvleft,
7743 &mvtot, mvname) >= 5) {
7744 /* The stat01: line is from Crafty (9.29+) in response
7745 to the "." command */
7746 programStats.seen_stat = 1;
7747 cps->maybeThinking = TRUE;
7749 if (programStats.got_only_move || !appData.periodicUpdates)
7752 programStats.depth = plylev;
7753 programStats.time = time;
7754 programStats.nodes = nodes;
7755 programStats.moves_left = mvleft;
7756 programStats.nr_moves = mvtot;
7757 strcpy(programStats.move_name, mvname);
7758 programStats.ok_to_send = 1;
7759 programStats.movelist[0] = '\0';
7761 SendProgramStatsToFrontend( cps, &programStats );
7765 } else if (strncmp(message,"++",2) == 0) {
7766 /* Crafty 9.29+ outputs this */
7767 programStats.got_fail = 2;
7770 } else if (strncmp(message,"--",2) == 0) {
7771 /* Crafty 9.29+ outputs this */
7772 programStats.got_fail = 1;
7775 } else if (thinkOutput[0] != NULLCHAR &&
7776 strncmp(message, " ", 4) == 0) {
7777 unsigned message_len;
7780 while (*p && *p == ' ') p++;
7782 message_len = strlen( p );
7784 /* [AS] Avoid buffer overflow */
7785 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7786 strcat(thinkOutput, " ");
7787 strcat(thinkOutput, p);
7790 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7791 strcat(programStats.movelist, " ");
7792 strcat(programStats.movelist, p);
7795 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7796 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7797 DisplayMove(currentMove - 1);
7805 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7806 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7808 ChessProgramStats cpstats;
7810 if (plyext != ' ' && plyext != '\t') {
7814 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7815 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7816 curscore = -curscore;
7819 cpstats.depth = plylev;
7820 cpstats.nodes = nodes;
7821 cpstats.time = time;
7822 cpstats.score = curscore;
7823 cpstats.got_only_move = 0;
7824 cpstats.movelist[0] = '\0';
7826 if (buf1[0] != NULLCHAR) {
7827 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7830 cpstats.ok_to_send = 0;
7831 cpstats.line_is_book = 0;
7832 cpstats.nr_moves = 0;
7833 cpstats.moves_left = 0;
7835 SendProgramStatsToFrontend( cps, &cpstats );
7842 /* Parse a game score from the character string "game", and
7843 record it as the history of the current game. The game
7844 score is NOT assumed to start from the standard position.
7845 The display is not updated in any way.
7848 ParseGameHistory(game)
7852 int fromX, fromY, toX, toY, boardIndex;
7857 if (appData.debugMode)
7858 fprintf(debugFP, "Parsing game history: %s\n", game);
7860 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7861 gameInfo.site = StrSave(appData.icsHost);
7862 gameInfo.date = PGNDate();
7863 gameInfo.round = StrSave("-");
7865 /* Parse out names of players */
7866 while (*game == ' ') game++;
7868 while (*game != ' ') *p++ = *game++;
7870 gameInfo.white = StrSave(buf);
7871 while (*game == ' ') game++;
7873 while (*game != ' ' && *game != '\n') *p++ = *game++;
7875 gameInfo.black = StrSave(buf);
7878 boardIndex = blackPlaysFirst ? 1 : 0;
7881 yyboardindex = boardIndex;
7882 moveType = (ChessMove) yylex();
7884 case IllegalMove: /* maybe suicide chess, etc. */
7885 if (appData.debugMode) {
7886 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7887 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7888 setbuf(debugFP, NULL);
7890 case WhitePromotionChancellor:
7891 case BlackPromotionChancellor:
7892 case WhitePromotionArchbishop:
7893 case BlackPromotionArchbishop:
7894 case WhitePromotionQueen:
7895 case BlackPromotionQueen:
7896 case WhitePromotionRook:
7897 case BlackPromotionRook:
7898 case WhitePromotionBishop:
7899 case BlackPromotionBishop:
7900 case WhitePromotionKnight:
7901 case BlackPromotionKnight:
7902 case WhitePromotionKing:
7903 case BlackPromotionKing:
7905 case WhiteCapturesEnPassant:
7906 case BlackCapturesEnPassant:
7907 case WhiteKingSideCastle:
7908 case WhiteQueenSideCastle:
7909 case BlackKingSideCastle:
7910 case BlackQueenSideCastle:
7911 case WhiteKingSideCastleWild:
7912 case WhiteQueenSideCastleWild:
7913 case BlackKingSideCastleWild:
7914 case BlackQueenSideCastleWild:
7916 case WhiteHSideCastleFR:
7917 case WhiteASideCastleFR:
7918 case BlackHSideCastleFR:
7919 case BlackASideCastleFR:
7921 fromX = currentMoveString[0] - AAA;
7922 fromY = currentMoveString[1] - ONE;
7923 toX = currentMoveString[2] - AAA;
7924 toY = currentMoveString[3] - ONE;
7925 promoChar = currentMoveString[4];
7929 fromX = moveType == WhiteDrop ?
7930 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7931 (int) CharToPiece(ToLower(currentMoveString[0]));
7933 toX = currentMoveString[2] - AAA;
7934 toY = currentMoveString[3] - ONE;
7935 promoChar = NULLCHAR;
7939 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7940 if (appData.debugMode) {
7941 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7942 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7943 setbuf(debugFP, NULL);
7945 DisplayError(buf, 0);
7947 case ImpossibleMove:
7949 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7950 if (appData.debugMode) {
7951 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7952 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7953 setbuf(debugFP, NULL);
7955 DisplayError(buf, 0);
7957 case (ChessMove) 0: /* end of file */
7958 if (boardIndex < backwardMostMove) {
7959 /* Oops, gap. How did that happen? */
7960 DisplayError(_("Gap in move list"), 0);
7963 backwardMostMove = blackPlaysFirst ? 1 : 0;
7964 if (boardIndex > forwardMostMove) {
7965 forwardMostMove = boardIndex;
7969 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7970 strcat(parseList[boardIndex-1], " ");
7971 strcat(parseList[boardIndex-1], yy_text);
7983 case GameUnfinished:
7984 if (gameMode == IcsExamining) {
7985 if (boardIndex < backwardMostMove) {
7986 /* Oops, gap. How did that happen? */
7989 backwardMostMove = blackPlaysFirst ? 1 : 0;
7992 gameInfo.result = moveType;
7993 p = strchr(yy_text, '{');
7994 if (p == NULL) p = strchr(yy_text, '(');
7997 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7999 q = strchr(p, *p == '{' ? '}' : ')');
8000 if (q != NULL) *q = NULLCHAR;
8003 gameInfo.resultDetails = StrSave(p);
8006 if (boardIndex >= forwardMostMove &&
8007 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8008 backwardMostMove = blackPlaysFirst ? 1 : 0;
8011 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8012 fromY, fromX, toY, toX, promoChar,
8013 parseList[boardIndex]);
8014 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8015 /* currentMoveString is set as a side-effect of yylex */
8016 strcpy(moveList[boardIndex], currentMoveString);
8017 strcat(moveList[boardIndex], "\n");
8019 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8020 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8026 if(gameInfo.variant != VariantShogi)
8027 strcat(parseList[boardIndex - 1], "+");
8031 strcat(parseList[boardIndex - 1], "#");
8038 /* Apply a move to the given board */
8040 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8041 int fromX, fromY, toX, toY;
8045 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8046 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8048 /* [HGM] compute & store e.p. status and castling rights for new position */
8049 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8052 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8053 oldEP = (signed char)board[EP_STATUS];
8054 board[EP_STATUS] = EP_NONE;
8056 if( board[toY][toX] != EmptySquare )
8057 board[EP_STATUS] = EP_CAPTURE;
8059 if( board[fromY][fromX] == WhitePawn ) {
8060 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8061 board[EP_STATUS] = EP_PAWN_MOVE;
8063 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8064 gameInfo.variant != VariantBerolina || toX < fromX)
8065 board[EP_STATUS] = toX | berolina;
8066 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8067 gameInfo.variant != VariantBerolina || toX > fromX)
8068 board[EP_STATUS] = toX;
8071 if( board[fromY][fromX] == BlackPawn ) {
8072 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8073 board[EP_STATUS] = EP_PAWN_MOVE;
8074 if( toY-fromY== -2) {
8075 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8076 gameInfo.variant != VariantBerolina || toX < fromX)
8077 board[EP_STATUS] = toX | berolina;
8078 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8079 gameInfo.variant != VariantBerolina || toX > fromX)
8080 board[EP_STATUS] = toX;
8084 for(i=0; i<nrCastlingRights; i++) {
8085 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8086 board[CASTLING][i] == toX && castlingRank[i] == toY
8087 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8092 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8093 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8094 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8096 if (fromX == toX && fromY == toY) return;
8098 if (fromY == DROP_RANK) {
8100 piece = board[toY][toX] = (ChessSquare) fromX;
8102 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8103 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8104 if(gameInfo.variant == VariantKnightmate)
8105 king += (int) WhiteUnicorn - (int) WhiteKing;
8107 /* Code added by Tord: */
8108 /* FRC castling assumed when king captures friendly rook. */
8109 if (board[fromY][fromX] == WhiteKing &&
8110 board[toY][toX] == WhiteRook) {
8111 board[fromY][fromX] = EmptySquare;
8112 board[toY][toX] = EmptySquare;
8114 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8116 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8118 } else if (board[fromY][fromX] == BlackKing &&
8119 board[toY][toX] == BlackRook) {
8120 board[fromY][fromX] = EmptySquare;
8121 board[toY][toX] = EmptySquare;
8123 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8125 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8127 /* End of code added by Tord */
8129 } else if (board[fromY][fromX] == king
8130 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8131 && toY == fromY && toX > fromX+1) {
8132 board[fromY][fromX] = EmptySquare;
8133 board[toY][toX] = king;
8134 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8135 board[fromY][BOARD_RGHT-1] = EmptySquare;
8136 } else if (board[fromY][fromX] == king
8137 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8138 && toY == fromY && toX < fromX-1) {
8139 board[fromY][fromX] = EmptySquare;
8140 board[toY][toX] = king;
8141 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8142 board[fromY][BOARD_LEFT] = EmptySquare;
8143 } else if (board[fromY][fromX] == WhitePawn
8144 && toY >= BOARD_HEIGHT-promoRank
8145 && gameInfo.variant != VariantXiangqi
8147 /* white pawn promotion */
8148 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8149 if (board[toY][toX] == EmptySquare) {
8150 board[toY][toX] = WhiteQueen;
8152 if(gameInfo.variant==VariantBughouse ||
8153 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8154 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8155 board[fromY][fromX] = EmptySquare;
8156 } else if ((fromY == BOARD_HEIGHT-4)
8158 && gameInfo.variant != VariantXiangqi
8159 && gameInfo.variant != VariantBerolina
8160 && (board[fromY][fromX] == WhitePawn)
8161 && (board[toY][toX] == EmptySquare)) {
8162 board[fromY][fromX] = EmptySquare;
8163 board[toY][toX] = WhitePawn;
8164 captured = board[toY - 1][toX];
8165 board[toY - 1][toX] = EmptySquare;
8166 } else if ((fromY == BOARD_HEIGHT-4)
8168 && gameInfo.variant == VariantBerolina
8169 && (board[fromY][fromX] == WhitePawn)
8170 && (board[toY][toX] == EmptySquare)) {
8171 board[fromY][fromX] = EmptySquare;
8172 board[toY][toX] = WhitePawn;
8173 if(oldEP & EP_BEROLIN_A) {
8174 captured = board[fromY][fromX-1];
8175 board[fromY][fromX-1] = EmptySquare;
8176 }else{ captured = board[fromY][fromX+1];
8177 board[fromY][fromX+1] = EmptySquare;
8179 } else if (board[fromY][fromX] == king
8180 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8181 && toY == fromY && toX > fromX+1) {
8182 board[fromY][fromX] = EmptySquare;
8183 board[toY][toX] = king;
8184 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8185 board[fromY][BOARD_RGHT-1] = EmptySquare;
8186 } else if (board[fromY][fromX] == king
8187 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8188 && toY == fromY && toX < fromX-1) {
8189 board[fromY][fromX] = EmptySquare;
8190 board[toY][toX] = king;
8191 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8192 board[fromY][BOARD_LEFT] = EmptySquare;
8193 } else if (fromY == 7 && fromX == 3
8194 && board[fromY][fromX] == BlackKing
8195 && toY == 7 && toX == 5) {
8196 board[fromY][fromX] = EmptySquare;
8197 board[toY][toX] = BlackKing;
8198 board[fromY][7] = EmptySquare;
8199 board[toY][4] = BlackRook;
8200 } else if (fromY == 7 && fromX == 3
8201 && board[fromY][fromX] == BlackKing
8202 && toY == 7 && toX == 1) {
8203 board[fromY][fromX] = EmptySquare;
8204 board[toY][toX] = BlackKing;
8205 board[fromY][0] = EmptySquare;
8206 board[toY][2] = BlackRook;
8207 } else if (board[fromY][fromX] == BlackPawn
8209 && gameInfo.variant != VariantXiangqi
8211 /* black pawn promotion */
8212 board[toY][toX] = CharToPiece(ToLower(promoChar));
8213 if (board[toY][toX] == EmptySquare) {
8214 board[toY][toX] = BlackQueen;
8216 if(gameInfo.variant==VariantBughouse ||
8217 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8218 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8219 board[fromY][fromX] = EmptySquare;
8220 } else if ((fromY == 3)
8222 && gameInfo.variant != VariantXiangqi
8223 && gameInfo.variant != VariantBerolina
8224 && (board[fromY][fromX] == BlackPawn)
8225 && (board[toY][toX] == EmptySquare)) {
8226 board[fromY][fromX] = EmptySquare;
8227 board[toY][toX] = BlackPawn;
8228 captured = board[toY + 1][toX];
8229 board[toY + 1][toX] = EmptySquare;
8230 } else if ((fromY == 3)
8232 && gameInfo.variant == VariantBerolina
8233 && (board[fromY][fromX] == BlackPawn)
8234 && (board[toY][toX] == EmptySquare)) {
8235 board[fromY][fromX] = EmptySquare;
8236 board[toY][toX] = BlackPawn;
8237 if(oldEP & EP_BEROLIN_A) {
8238 captured = board[fromY][fromX-1];
8239 board[fromY][fromX-1] = EmptySquare;
8240 }else{ captured = board[fromY][fromX+1];
8241 board[fromY][fromX+1] = EmptySquare;
8244 board[toY][toX] = board[fromY][fromX];
8245 board[fromY][fromX] = EmptySquare;
8248 /* [HGM] now we promote for Shogi, if needed */
8249 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8250 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8253 if (gameInfo.holdingsWidth != 0) {
8255 /* !!A lot more code needs to be written to support holdings */
8256 /* [HGM] OK, so I have written it. Holdings are stored in the */
8257 /* penultimate board files, so they are automaticlly stored */
8258 /* in the game history. */
8259 if (fromY == DROP_RANK) {
8260 /* Delete from holdings, by decreasing count */
8261 /* and erasing image if necessary */
8263 if(p < (int) BlackPawn) { /* white drop */
8264 p -= (int)WhitePawn;
8265 p = PieceToNumber((ChessSquare)p);
8266 if(p >= gameInfo.holdingsSize) p = 0;
8267 if(--board[p][BOARD_WIDTH-2] <= 0)
8268 board[p][BOARD_WIDTH-1] = EmptySquare;
8269 if((int)board[p][BOARD_WIDTH-2] < 0)
8270 board[p][BOARD_WIDTH-2] = 0;
8271 } else { /* black drop */
8272 p -= (int)BlackPawn;
8273 p = PieceToNumber((ChessSquare)p);
8274 if(p >= gameInfo.holdingsSize) p = 0;
8275 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8276 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8277 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8278 board[BOARD_HEIGHT-1-p][1] = 0;
8281 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8282 && gameInfo.variant != VariantBughouse ) {
8283 /* [HGM] holdings: Add to holdings, if holdings exist */
8284 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8285 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8286 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8289 if (p >= (int) BlackPawn) {
8290 p -= (int)BlackPawn;
8291 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8292 /* in Shogi restore piece to its original first */
8293 captured = (ChessSquare) (DEMOTED captured);
8296 p = PieceToNumber((ChessSquare)p);
8297 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8298 board[p][BOARD_WIDTH-2]++;
8299 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8301 p -= (int)WhitePawn;
8302 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8303 captured = (ChessSquare) (DEMOTED captured);
8306 p = PieceToNumber((ChessSquare)p);
8307 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8308 board[BOARD_HEIGHT-1-p][1]++;
8309 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8312 } else if (gameInfo.variant == VariantAtomic) {
8313 if (captured != EmptySquare) {
8315 for (y = toY-1; y <= toY+1; y++) {
8316 for (x = toX-1; x <= toX+1; x++) {
8317 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8318 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8319 board[y][x] = EmptySquare;
8323 board[toY][toX] = EmptySquare;
8326 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8327 /* [HGM] Shogi promotions */
8328 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8331 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8332 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8333 // [HGM] superchess: take promotion piece out of holdings
8334 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8335 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8336 if(!--board[k][BOARD_WIDTH-2])
8337 board[k][BOARD_WIDTH-1] = EmptySquare;
8339 if(!--board[BOARD_HEIGHT-1-k][1])
8340 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8346 /* Updates forwardMostMove */
8348 MakeMove(fromX, fromY, toX, toY, promoChar)
8349 int fromX, fromY, toX, toY;
8352 // forwardMostMove++; // [HGM] bare: moved downstream
8354 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8355 int timeLeft; static int lastLoadFlag=0; int king, piece;
8356 piece = boards[forwardMostMove][fromY][fromX];
8357 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8358 if(gameInfo.variant == VariantKnightmate)
8359 king += (int) WhiteUnicorn - (int) WhiteKing;
8360 if(forwardMostMove == 0) {
8362 fprintf(serverMoves, "%s;", second.tidy);
8363 fprintf(serverMoves, "%s;", first.tidy);
8364 if(!blackPlaysFirst)
8365 fprintf(serverMoves, "%s;", second.tidy);
8366 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8367 lastLoadFlag = loadFlag;
8369 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8370 // print castling suffix
8371 if( toY == fromY && piece == king ) {
8373 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8375 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8378 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8379 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8380 boards[forwardMostMove][toY][toX] == EmptySquare
8382 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8384 if(promoChar != NULLCHAR)
8385 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8387 fprintf(serverMoves, "/%d/%d",
8388 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8389 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8390 else timeLeft = blackTimeRemaining/1000;
8391 fprintf(serverMoves, "/%d", timeLeft);
8393 fflush(serverMoves);
8396 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8397 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8401 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8402 if (commentList[forwardMostMove+1] != NULL) {
8403 free(commentList[forwardMostMove+1]);
8404 commentList[forwardMostMove+1] = NULL;
8406 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8407 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8408 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8409 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8410 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8411 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8412 gameInfo.result = GameUnfinished;
8413 if (gameInfo.resultDetails != NULL) {
8414 free(gameInfo.resultDetails);
8415 gameInfo.resultDetails = NULL;
8417 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8418 moveList[forwardMostMove - 1]);
8419 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8420 PosFlags(forwardMostMove - 1),
8421 fromY, fromX, toY, toX, promoChar,
8422 parseList[forwardMostMove - 1]);
8423 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8429 if(gameInfo.variant != VariantShogi)
8430 strcat(parseList[forwardMostMove - 1], "+");
8434 strcat(parseList[forwardMostMove - 1], "#");
8437 if (appData.debugMode) {
8438 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8443 /* Updates currentMove if not pausing */
8445 ShowMove(fromX, fromY, toX, toY)
8447 int instant = (gameMode == PlayFromGameFile) ?
8448 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8449 if(appData.noGUI) return;
8450 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8452 if (forwardMostMove == currentMove + 1) {
8453 AnimateMove(boards[forwardMostMove - 1],
8454 fromX, fromY, toX, toY);
8456 if (appData.highlightLastMove) {
8457 SetHighlights(fromX, fromY, toX, toY);
8460 currentMove = forwardMostMove;
8463 if (instant) return;
8465 DisplayMove(currentMove - 1);
8466 DrawPosition(FALSE, boards[currentMove]);
8467 DisplayBothClocks();
8468 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8471 void SendEgtPath(ChessProgramState *cps)
8472 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8473 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8475 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8478 char c, *q = name+1, *r, *s;
8480 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8481 while(*p && *p != ',') *q++ = *p++;
8483 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8484 strcmp(name, ",nalimov:") == 0 ) {
8485 // take nalimov path from the menu-changeable option first, if it is defined
8486 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8487 SendToProgram(buf,cps); // send egtbpath command for nalimov
8489 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8490 (s = StrStr(appData.egtFormats, name)) != NULL) {
8491 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8492 s = r = StrStr(s, ":") + 1; // beginning of path info
8493 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8494 c = *r; *r = 0; // temporarily null-terminate path info
8495 *--q = 0; // strip of trailig ':' from name
8496 sprintf(buf, "egtpath %s %s\n", name+1, s);
8498 SendToProgram(buf,cps); // send egtbpath command for this format
8500 if(*p == ',') p++; // read away comma to position for next format name
8505 InitChessProgram(cps, setup)
8506 ChessProgramState *cps;
8507 int setup; /* [HGM] needed to setup FRC opening position */
8509 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8510 if (appData.noChessProgram) return;
8511 hintRequested = FALSE;
8512 bookRequested = FALSE;
8514 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8515 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8516 if(cps->memSize) { /* [HGM] memory */
8517 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8518 SendToProgram(buf, cps);
8520 SendEgtPath(cps); /* [HGM] EGT */
8521 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8522 sprintf(buf, "cores %d\n", appData.smpCores);
8523 SendToProgram(buf, cps);
8526 SendToProgram(cps->initString, cps);
8527 if (gameInfo.variant != VariantNormal &&
8528 gameInfo.variant != VariantLoadable
8529 /* [HGM] also send variant if board size non-standard */
8530 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8532 char *v = VariantName(gameInfo.variant);
8533 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8534 /* [HGM] in protocol 1 we have to assume all variants valid */
8535 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8536 DisplayFatalError(buf, 0, 1);
8540 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8541 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8542 if( gameInfo.variant == VariantXiangqi )
8543 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8544 if( gameInfo.variant == VariantShogi )
8545 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8546 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8547 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8548 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8549 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8550 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8551 if( gameInfo.variant == VariantCourier )
8552 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8553 if( gameInfo.variant == VariantSuper )
8554 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8555 if( gameInfo.variant == VariantGreat )
8556 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8559 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8560 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8561 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8562 if(StrStr(cps->variants, b) == NULL) {
8563 // specific sized variant not known, check if general sizing allowed
8564 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8565 if(StrStr(cps->variants, "boardsize") == NULL) {
8566 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8567 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8568 DisplayFatalError(buf, 0, 1);
8571 /* [HGM] here we really should compare with the maximum supported board size */
8574 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8575 sprintf(buf, "variant %s\n", b);
8576 SendToProgram(buf, cps);
8578 currentlyInitializedVariant = gameInfo.variant;
8580 /* [HGM] send opening position in FRC to first engine */
8582 SendToProgram("force\n", cps);
8584 /* engine is now in force mode! Set flag to wake it up after first move. */
8585 setboardSpoiledMachineBlack = 1;
8589 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8590 SendToProgram(buf, cps);
8592 cps->maybeThinking = FALSE;
8593 cps->offeredDraw = 0;
8594 if (!appData.icsActive) {
8595 SendTimeControl(cps, movesPerSession, timeControl,
8596 timeIncrement, appData.searchDepth,
8599 if (appData.showThinking
8600 // [HGM] thinking: four options require thinking output to be sent
8601 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8603 SendToProgram("post\n", cps);
8605 SendToProgram("hard\n", cps);
8606 if (!appData.ponderNextMove) {
8607 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8608 it without being sure what state we are in first. "hard"
8609 is not a toggle, so that one is OK.
8611 SendToProgram("easy\n", cps);
8614 sprintf(buf, "ping %d\n", ++cps->lastPing);
8615 SendToProgram(buf, cps);
8617 cps->initDone = TRUE;
8622 StartChessProgram(cps)
8623 ChessProgramState *cps;
8628 if (appData.noChessProgram) return;
8629 cps->initDone = FALSE;
8631 if (strcmp(cps->host, "localhost") == 0) {
8632 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8633 } else if (*appData.remoteShell == NULLCHAR) {
8634 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8636 if (*appData.remoteUser == NULLCHAR) {
8637 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8640 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8641 cps->host, appData.remoteUser, cps->program);
8643 err = StartChildProcess(buf, "", &cps->pr);
8647 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8648 DisplayFatalError(buf, err, 1);
8654 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8655 if (cps->protocolVersion > 1) {
8656 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8657 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8658 cps->comboCnt = 0; // and values of combo boxes
8659 SendToProgram(buf, cps);
8661 SendToProgram("xboard\n", cps);
8667 TwoMachinesEventIfReady P((void))
8669 if (first.lastPing != first.lastPong) {
8670 DisplayMessage("", _("Waiting for first chess program"));
8671 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8674 if (second.lastPing != second.lastPong) {
8675 DisplayMessage("", _("Waiting for second chess program"));
8676 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8684 NextMatchGame P((void))
8686 int index; /* [HGM] autoinc: step load index during match */
8688 if (*appData.loadGameFile != NULLCHAR) {
8689 index = appData.loadGameIndex;
8690 if(index < 0) { // [HGM] autoinc
8691 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8692 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8694 LoadGameFromFile(appData.loadGameFile,
8696 appData.loadGameFile, FALSE);
8697 } else if (*appData.loadPositionFile != NULLCHAR) {
8698 index = appData.loadPositionIndex;
8699 if(index < 0) { // [HGM] autoinc
8700 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8701 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8703 LoadPositionFromFile(appData.loadPositionFile,
8705 appData.loadPositionFile);
8707 TwoMachinesEventIfReady();
8710 void UserAdjudicationEvent( int result )
8712 ChessMove gameResult = GameIsDrawn;
8715 gameResult = WhiteWins;
8717 else if( result < 0 ) {
8718 gameResult = BlackWins;
8721 if( gameMode == TwoMachinesPlay ) {
8722 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8727 // [HGM] save: calculate checksum of game to make games easily identifiable
8728 int StringCheckSum(char *s)
8731 if(s==NULL) return 0;
8732 while(*s) i = i*259 + *s++;
8739 for(i=backwardMostMove; i<forwardMostMove; i++) {
8740 sum += pvInfoList[i].depth;
8741 sum += StringCheckSum(parseList[i]);
8742 sum += StringCheckSum(commentList[i]);
8745 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8746 return sum + StringCheckSum(commentList[i]);
8747 } // end of save patch
8750 GameEnds(result, resultDetails, whosays)
8752 char *resultDetails;
8755 GameMode nextGameMode;
8759 if(endingGame) return; /* [HGM] crash: forbid recursion */
8762 if (appData.debugMode) {
8763 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8764 result, resultDetails ? resultDetails : "(null)", whosays);
8767 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8769 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8770 /* If we are playing on ICS, the server decides when the
8771 game is over, but the engine can offer to draw, claim
8775 if (appData.zippyPlay && first.initDone) {
8776 if (result == GameIsDrawn) {
8777 /* In case draw still needs to be claimed */
8778 SendToICS(ics_prefix);
8779 SendToICS("draw\n");
8780 } else if (StrCaseStr(resultDetails, "resign")) {
8781 SendToICS(ics_prefix);
8782 SendToICS("resign\n");
8786 endingGame = 0; /* [HGM] crash */
8790 /* If we're loading the game from a file, stop */
8791 if (whosays == GE_FILE) {
8792 (void) StopLoadGameTimer();
8796 /* Cancel draw offers */
8797 first.offeredDraw = second.offeredDraw = 0;
8799 /* If this is an ICS game, only ICS can really say it's done;
8800 if not, anyone can. */
8801 isIcsGame = (gameMode == IcsPlayingWhite ||
8802 gameMode == IcsPlayingBlack ||
8803 gameMode == IcsObserving ||
8804 gameMode == IcsExamining);
8806 if (!isIcsGame || whosays == GE_ICS) {
8807 /* OK -- not an ICS game, or ICS said it was done */
8809 if (!isIcsGame && !appData.noChessProgram)
8810 SetUserThinkingEnables();
8812 /* [HGM] if a machine claims the game end we verify this claim */
8813 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8814 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8816 ChessMove trueResult = (ChessMove) -1;
8818 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8819 first.twoMachinesColor[0] :
8820 second.twoMachinesColor[0] ;
8822 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8823 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8824 /* [HGM] verify: engine mate claims accepted if they were flagged */
8825 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8827 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8828 /* [HGM] verify: engine mate claims accepted if they were flagged */
8829 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8831 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8832 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8835 // now verify win claims, but not in drop games, as we don't understand those yet
8836 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8837 || gameInfo.variant == VariantGreat) &&
8838 (result == WhiteWins && claimer == 'w' ||
8839 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8840 if (appData.debugMode) {
8841 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8842 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8844 if(result != trueResult) {
8845 sprintf(buf, "False win claim: '%s'", resultDetails);
8846 result = claimer == 'w' ? BlackWins : WhiteWins;
8847 resultDetails = buf;
8850 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8851 && (forwardMostMove <= backwardMostMove ||
8852 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8853 (claimer=='b')==(forwardMostMove&1))
8855 /* [HGM] verify: draws that were not flagged are false claims */
8856 sprintf(buf, "False draw claim: '%s'", resultDetails);
8857 result = claimer == 'w' ? BlackWins : WhiteWins;
8858 resultDetails = buf;
8860 /* (Claiming a loss is accepted no questions asked!) */
8862 /* [HGM] bare: don't allow bare King to win */
8863 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8864 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8865 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8866 && result != GameIsDrawn)
8867 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8868 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8869 int p = (signed char)boards[forwardMostMove][i][j] - color;
8870 if(p >= 0 && p <= (int)WhiteKing) k++;
8872 if (appData.debugMode) {
8873 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8874 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8877 result = GameIsDrawn;
8878 sprintf(buf, "%s but bare king", resultDetails);
8879 resultDetails = buf;
8885 if(serverMoves != NULL && !loadFlag) { char c = '=';
8886 if(result==WhiteWins) c = '+';
8887 if(result==BlackWins) c = '-';
8888 if(resultDetails != NULL)
8889 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8891 if (resultDetails != NULL) {
8892 gameInfo.result = result;
8893 gameInfo.resultDetails = StrSave(resultDetails);
8895 /* display last move only if game was not loaded from file */
8896 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8897 DisplayMove(currentMove - 1);
8899 if (forwardMostMove != 0) {
8900 if (gameMode != PlayFromGameFile && gameMode != EditGame
8901 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8903 if (*appData.saveGameFile != NULLCHAR) {
8904 SaveGameToFile(appData.saveGameFile, TRUE);
8905 } else if (appData.autoSaveGames) {
8908 if (*appData.savePositionFile != NULLCHAR) {
8909 SavePositionToFile(appData.savePositionFile);
8914 /* Tell program how game ended in case it is learning */
8915 /* [HGM] Moved this to after saving the PGN, just in case */
8916 /* engine died and we got here through time loss. In that */
8917 /* case we will get a fatal error writing the pipe, which */
8918 /* would otherwise lose us the PGN. */
8919 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8920 /* output during GameEnds should never be fatal anymore */
8921 if (gameMode == MachinePlaysWhite ||
8922 gameMode == MachinePlaysBlack ||
8923 gameMode == TwoMachinesPlay ||
8924 gameMode == IcsPlayingWhite ||
8925 gameMode == IcsPlayingBlack ||
8926 gameMode == BeginningOfGame) {
8928 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8930 if (first.pr != NoProc) {
8931 SendToProgram(buf, &first);
8933 if (second.pr != NoProc &&
8934 gameMode == TwoMachinesPlay) {
8935 SendToProgram(buf, &second);
8940 if (appData.icsActive) {
8941 if (appData.quietPlay &&
8942 (gameMode == IcsPlayingWhite ||
8943 gameMode == IcsPlayingBlack)) {
8944 SendToICS(ics_prefix);
8945 SendToICS("set shout 1\n");
8947 nextGameMode = IcsIdle;
8948 ics_user_moved = FALSE;
8949 /* clean up premove. It's ugly when the game has ended and the
8950 * premove highlights are still on the board.
8954 ClearPremoveHighlights();
8955 DrawPosition(FALSE, boards[currentMove]);
8957 if (whosays == GE_ICS) {
8960 if (gameMode == IcsPlayingWhite)
8962 else if(gameMode == IcsPlayingBlack)
8966 if (gameMode == IcsPlayingBlack)
8968 else if(gameMode == IcsPlayingWhite)
8975 PlayIcsUnfinishedSound();
8978 } else if (gameMode == EditGame ||
8979 gameMode == PlayFromGameFile ||
8980 gameMode == AnalyzeMode ||
8981 gameMode == AnalyzeFile) {
8982 nextGameMode = gameMode;
8984 nextGameMode = EndOfGame;
8989 nextGameMode = gameMode;
8992 if (appData.noChessProgram) {
8993 gameMode = nextGameMode;
8995 endingGame = 0; /* [HGM] crash */
9000 /* Put first chess program into idle state */
9001 if (first.pr != NoProc &&
9002 (gameMode == MachinePlaysWhite ||
9003 gameMode == MachinePlaysBlack ||
9004 gameMode == TwoMachinesPlay ||
9005 gameMode == IcsPlayingWhite ||
9006 gameMode == IcsPlayingBlack ||
9007 gameMode == BeginningOfGame)) {
9008 SendToProgram("force\n", &first);
9009 if (first.usePing) {
9011 sprintf(buf, "ping %d\n", ++first.lastPing);
9012 SendToProgram(buf, &first);
9015 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9016 /* Kill off first chess program */
9017 if (first.isr != NULL)
9018 RemoveInputSource(first.isr);
9021 if (first.pr != NoProc) {
9023 DoSleep( appData.delayBeforeQuit );
9024 SendToProgram("quit\n", &first);
9025 DoSleep( appData.delayAfterQuit );
9026 DestroyChildProcess(first.pr, first.useSigterm);
9031 /* Put second chess program into idle state */
9032 if (second.pr != NoProc &&
9033 gameMode == TwoMachinesPlay) {
9034 SendToProgram("force\n", &second);
9035 if (second.usePing) {
9037 sprintf(buf, "ping %d\n", ++second.lastPing);
9038 SendToProgram(buf, &second);
9041 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9042 /* Kill off second chess program */
9043 if (second.isr != NULL)
9044 RemoveInputSource(second.isr);
9047 if (second.pr != NoProc) {
9048 DoSleep( appData.delayBeforeQuit );
9049 SendToProgram("quit\n", &second);
9050 DoSleep( appData.delayAfterQuit );
9051 DestroyChildProcess(second.pr, second.useSigterm);
9056 if (matchMode && gameMode == TwoMachinesPlay) {
9059 if (first.twoMachinesColor[0] == 'w') {
9066 if (first.twoMachinesColor[0] == 'b') {
9075 if (matchGame < appData.matchGames) {
9077 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9078 tmp = first.twoMachinesColor;
9079 first.twoMachinesColor = second.twoMachinesColor;
9080 second.twoMachinesColor = tmp;
9082 gameMode = nextGameMode;
9084 if(appData.matchPause>10000 || appData.matchPause<10)
9085 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9086 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9087 endingGame = 0; /* [HGM] crash */
9091 gameMode = nextGameMode;
9092 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9093 first.tidy, second.tidy,
9094 first.matchWins, second.matchWins,
9095 appData.matchGames - (first.matchWins + second.matchWins));
9096 DisplayFatalError(buf, 0, 0);
9099 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9100 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9102 gameMode = nextGameMode;
9104 endingGame = 0; /* [HGM] crash */
9107 /* Assumes program was just initialized (initString sent).
9108 Leaves program in force mode. */
9110 FeedMovesToProgram(cps, upto)
9111 ChessProgramState *cps;
9116 if (appData.debugMode)
9117 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9118 startedFromSetupPosition ? "position and " : "",
9119 backwardMostMove, upto, cps->which);
9120 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9121 // [HGM] variantswitch: make engine aware of new variant
9122 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9123 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9124 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9125 SendToProgram(buf, cps);
9126 currentlyInitializedVariant = gameInfo.variant;
9128 SendToProgram("force\n", cps);
9129 if (startedFromSetupPosition) {
9130 SendBoard(cps, backwardMostMove);
9131 if (appData.debugMode) {
9132 fprintf(debugFP, "feedMoves\n");
9135 for (i = backwardMostMove; i < upto; i++) {
9136 SendMoveToProgram(i, cps);
9142 ResurrectChessProgram()
9144 /* The chess program may have exited.
9145 If so, restart it and feed it all the moves made so far. */
9147 if (appData.noChessProgram || first.pr != NoProc) return;
9149 StartChessProgram(&first);
9150 InitChessProgram(&first, FALSE);
9151 FeedMovesToProgram(&first, currentMove);
9153 if (!first.sendTime) {
9154 /* can't tell gnuchess what its clock should read,
9155 so we bow to its notion. */
9157 timeRemaining[0][currentMove] = whiteTimeRemaining;
9158 timeRemaining[1][currentMove] = blackTimeRemaining;
9161 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9162 appData.icsEngineAnalyze) && first.analysisSupport) {
9163 SendToProgram("analyze\n", &first);
9164 first.analyzing = TRUE;
9177 if (appData.debugMode) {
9178 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9179 redraw, init, gameMode);
9181 CleanupTail(); // [HGM] vari: delete any stored variations
9182 pausing = pauseExamInvalid = FALSE;
9183 startedFromSetupPosition = blackPlaysFirst = FALSE;
9185 whiteFlag = blackFlag = FALSE;
9186 userOfferedDraw = FALSE;
9187 hintRequested = bookRequested = FALSE;
9188 first.maybeThinking = FALSE;
9189 second.maybeThinking = FALSE;
9190 first.bookSuspend = FALSE; // [HGM] book
9191 second.bookSuspend = FALSE;
9192 thinkOutput[0] = NULLCHAR;
9193 lastHint[0] = NULLCHAR;
9194 ClearGameInfo(&gameInfo);
9195 gameInfo.variant = StringToVariant(appData.variant);
9196 ics_user_moved = ics_clock_paused = FALSE;
9197 ics_getting_history = H_FALSE;
9199 white_holding[0] = black_holding[0] = NULLCHAR;
9200 ClearProgramStats();
9201 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9205 flipView = appData.flipView;
9206 ClearPremoveHighlights();
9208 alarmSounded = FALSE;
9210 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9211 if(appData.serverMovesName != NULL) {
9212 /* [HGM] prepare to make moves file for broadcasting */
9213 clock_t t = clock();
9214 if(serverMoves != NULL) fclose(serverMoves);
9215 serverMoves = fopen(appData.serverMovesName, "r");
9216 if(serverMoves != NULL) {
9217 fclose(serverMoves);
9218 /* delay 15 sec before overwriting, so all clients can see end */
9219 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9221 serverMoves = fopen(appData.serverMovesName, "w");
9225 gameMode = BeginningOfGame;
9227 if(appData.icsActive) gameInfo.variant = VariantNormal;
9228 currentMove = forwardMostMove = backwardMostMove = 0;
9229 InitPosition(redraw);
9230 for (i = 0; i < MAX_MOVES; i++) {
9231 if (commentList[i] != NULL) {
9232 free(commentList[i]);
9233 commentList[i] = NULL;
9237 timeRemaining[0][0] = whiteTimeRemaining;
9238 timeRemaining[1][0] = blackTimeRemaining;
9239 if (first.pr == NULL) {
9240 StartChessProgram(&first);
9243 InitChessProgram(&first, startedFromSetupPosition);
9246 DisplayMessage("", "");
9247 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9248 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9255 if (!AutoPlayOneMove())
9257 if (matchMode || appData.timeDelay == 0)
9259 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9261 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9270 int fromX, fromY, toX, toY;
9272 if (appData.debugMode) {
9273 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9276 if (gameMode != PlayFromGameFile)
9279 if (currentMove >= forwardMostMove) {
9280 gameMode = EditGame;
9283 /* [AS] Clear current move marker at the end of a game */
9284 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9289 toX = moveList[currentMove][2] - AAA;
9290 toY = moveList[currentMove][3] - ONE;
9292 if (moveList[currentMove][1] == '@') {
9293 if (appData.highlightLastMove) {
9294 SetHighlights(-1, -1, toX, toY);
9297 fromX = moveList[currentMove][0] - AAA;
9298 fromY = moveList[currentMove][1] - ONE;
9300 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9302 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9304 if (appData.highlightLastMove) {
9305 SetHighlights(fromX, fromY, toX, toY);
9308 DisplayMove(currentMove);
9309 SendMoveToProgram(currentMove++, &first);
9310 DisplayBothClocks();
9311 DrawPosition(FALSE, boards[currentMove]);
9312 // [HGM] PV info: always display, routine tests if empty
9313 DisplayComment(currentMove - 1, commentList[currentMove]);
9319 LoadGameOneMove(readAhead)
9320 ChessMove readAhead;
9322 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9323 char promoChar = NULLCHAR;
9328 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9329 gameMode != AnalyzeMode && gameMode != Training) {
9334 yyboardindex = forwardMostMove;
9335 if (readAhead != (ChessMove)0) {
9336 moveType = readAhead;
9338 if (gameFileFP == NULL)
9340 moveType = (ChessMove) yylex();
9346 if (appData.debugMode)
9347 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9350 /* append the comment but don't display it */
9351 AppendComment(currentMove, p, FALSE);
9354 case WhiteCapturesEnPassant:
9355 case BlackCapturesEnPassant:
9356 case WhitePromotionChancellor:
9357 case BlackPromotionChancellor:
9358 case WhitePromotionArchbishop:
9359 case BlackPromotionArchbishop:
9360 case WhitePromotionCentaur:
9361 case BlackPromotionCentaur:
9362 case WhitePromotionQueen:
9363 case BlackPromotionQueen:
9364 case WhitePromotionRook:
9365 case BlackPromotionRook:
9366 case WhitePromotionBishop:
9367 case BlackPromotionBishop:
9368 case WhitePromotionKnight:
9369 case BlackPromotionKnight:
9370 case WhitePromotionKing:
9371 case BlackPromotionKing:
9373 case WhiteKingSideCastle:
9374 case WhiteQueenSideCastle:
9375 case BlackKingSideCastle:
9376 case BlackQueenSideCastle:
9377 case WhiteKingSideCastleWild:
9378 case WhiteQueenSideCastleWild:
9379 case BlackKingSideCastleWild:
9380 case BlackQueenSideCastleWild:
9382 case WhiteHSideCastleFR:
9383 case WhiteASideCastleFR:
9384 case BlackHSideCastleFR:
9385 case BlackASideCastleFR:
9387 if (appData.debugMode)
9388 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9389 fromX = currentMoveString[0] - AAA;
9390 fromY = currentMoveString[1] - ONE;
9391 toX = currentMoveString[2] - AAA;
9392 toY = currentMoveString[3] - ONE;
9393 promoChar = currentMoveString[4];
9398 if (appData.debugMode)
9399 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9400 fromX = moveType == WhiteDrop ?
9401 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9402 (int) CharToPiece(ToLower(currentMoveString[0]));
9404 toX = currentMoveString[2] - AAA;
9405 toY = currentMoveString[3] - ONE;
9411 case GameUnfinished:
9412 if (appData.debugMode)
9413 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9414 p = strchr(yy_text, '{');
9415 if (p == NULL) p = strchr(yy_text, '(');
9418 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9420 q = strchr(p, *p == '{' ? '}' : ')');
9421 if (q != NULL) *q = NULLCHAR;
9424 GameEnds(moveType, p, GE_FILE);
9426 if (cmailMsgLoaded) {
9428 flipView = WhiteOnMove(currentMove);
9429 if (moveType == GameUnfinished) flipView = !flipView;
9430 if (appData.debugMode)
9431 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9435 case (ChessMove) 0: /* end of file */
9436 if (appData.debugMode)
9437 fprintf(debugFP, "Parser hit end of file\n");
9438 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9444 if (WhiteOnMove(currentMove)) {
9445 GameEnds(BlackWins, "Black mates", GE_FILE);
9447 GameEnds(WhiteWins, "White mates", GE_FILE);
9451 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9458 if (lastLoadGameStart == GNUChessGame) {
9459 /* GNUChessGames have numbers, but they aren't move numbers */
9460 if (appData.debugMode)
9461 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9462 yy_text, (int) moveType);
9463 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9465 /* else fall thru */
9470 /* Reached start of next game in file */
9471 if (appData.debugMode)
9472 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9473 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9479 if (WhiteOnMove(currentMove)) {
9480 GameEnds(BlackWins, "Black mates", GE_FILE);
9482 GameEnds(WhiteWins, "White mates", GE_FILE);
9486 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9492 case PositionDiagram: /* should not happen; ignore */
9493 case ElapsedTime: /* ignore */
9494 case NAG: /* ignore */
9495 if (appData.debugMode)
9496 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9497 yy_text, (int) moveType);
9498 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9501 if (appData.testLegality) {
9502 if (appData.debugMode)
9503 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9504 sprintf(move, _("Illegal move: %d.%s%s"),
9505 (forwardMostMove / 2) + 1,
9506 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9507 DisplayError(move, 0);
9510 if (appData.debugMode)
9511 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9512 yy_text, currentMoveString);
9513 fromX = currentMoveString[0] - AAA;
9514 fromY = currentMoveString[1] - ONE;
9515 toX = currentMoveString[2] - AAA;
9516 toY = currentMoveString[3] - ONE;
9517 promoChar = currentMoveString[4];
9522 if (appData.debugMode)
9523 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9524 sprintf(move, _("Ambiguous move: %d.%s%s"),
9525 (forwardMostMove / 2) + 1,
9526 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9527 DisplayError(move, 0);
9532 case ImpossibleMove:
9533 if (appData.debugMode)
9534 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9535 sprintf(move, _("Illegal move: %d.%s%s"),
9536 (forwardMostMove / 2) + 1,
9537 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9538 DisplayError(move, 0);
9544 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9545 DrawPosition(FALSE, boards[currentMove]);
9546 DisplayBothClocks();
9547 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9548 DisplayComment(currentMove - 1, commentList[currentMove]);
9550 (void) StopLoadGameTimer();
9552 cmailOldMove = forwardMostMove;
9555 /* currentMoveString is set as a side-effect of yylex */
9556 strcat(currentMoveString, "\n");
9557 strcpy(moveList[forwardMostMove], currentMoveString);
9559 thinkOutput[0] = NULLCHAR;
9560 MakeMove(fromX, fromY, toX, toY, promoChar);
9561 currentMove = forwardMostMove;
9566 /* Load the nth game from the given file */
9568 LoadGameFromFile(filename, n, title, useList)
9572 /*Boolean*/ int useList;
9577 if (strcmp(filename, "-") == 0) {
9581 f = fopen(filename, "rb");
9583 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9584 DisplayError(buf, errno);
9588 if (fseek(f, 0, 0) == -1) {
9589 /* f is not seekable; probably a pipe */
9592 if (useList && n == 0) {
9593 int error = GameListBuild(f);
9595 DisplayError(_("Cannot build game list"), error);
9596 } else if (!ListEmpty(&gameList) &&
9597 ((ListGame *) gameList.tailPred)->number > 1) {
9598 GameListPopUp(f, title);
9605 return LoadGame(f, n, title, FALSE);
9610 MakeRegisteredMove()
9612 int fromX, fromY, toX, toY;
9614 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9615 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9618 if (appData.debugMode)
9619 fprintf(debugFP, "Restoring %s for game %d\n",
9620 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9622 thinkOutput[0] = NULLCHAR;
9623 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9624 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9625 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9626 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9627 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9628 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9629 MakeMove(fromX, fromY, toX, toY, promoChar);
9630 ShowMove(fromX, fromY, toX, toY);
9632 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9639 if (WhiteOnMove(currentMove)) {
9640 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9642 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9647 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9654 if (WhiteOnMove(currentMove)) {
9655 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9657 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9662 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9673 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9675 CmailLoadGame(f, gameNumber, title, useList)
9683 if (gameNumber > nCmailGames) {
9684 DisplayError(_("No more games in this message"), 0);
9687 if (f == lastLoadGameFP) {
9688 int offset = gameNumber - lastLoadGameNumber;
9690 cmailMsg[0] = NULLCHAR;
9691 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9692 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9693 nCmailMovesRegistered--;
9695 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9696 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9697 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9700 if (! RegisterMove()) return FALSE;
9704 retVal = LoadGame(f, gameNumber, title, useList);
9706 /* Make move registered during previous look at this game, if any */
9707 MakeRegisteredMove();
9709 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9710 commentList[currentMove]
9711 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9712 DisplayComment(currentMove - 1, commentList[currentMove]);
9718 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9723 int gameNumber = lastLoadGameNumber + offset;
9724 if (lastLoadGameFP == NULL) {
9725 DisplayError(_("No game has been loaded yet"), 0);
9728 if (gameNumber <= 0) {
9729 DisplayError(_("Can't back up any further"), 0);
9732 if (cmailMsgLoaded) {
9733 return CmailLoadGame(lastLoadGameFP, gameNumber,
9734 lastLoadGameTitle, lastLoadGameUseList);
9736 return LoadGame(lastLoadGameFP, gameNumber,
9737 lastLoadGameTitle, lastLoadGameUseList);
9743 /* Load the nth game from open file f */
9745 LoadGame(f, gameNumber, title, useList)
9753 int gn = gameNumber;
9754 ListGame *lg = NULL;
9757 GameMode oldGameMode;
9758 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9760 if (appData.debugMode)
9761 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9763 if (gameMode == Training )
9764 SetTrainingModeOff();
9766 oldGameMode = gameMode;
9767 if (gameMode != BeginningOfGame) {
9772 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9773 fclose(lastLoadGameFP);
9777 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9780 fseek(f, lg->offset, 0);
9781 GameListHighlight(gameNumber);
9785 DisplayError(_("Game number out of range"), 0);
9790 if (fseek(f, 0, 0) == -1) {
9791 if (f == lastLoadGameFP ?
9792 gameNumber == lastLoadGameNumber + 1 :
9796 DisplayError(_("Can't seek on game file"), 0);
9802 lastLoadGameNumber = gameNumber;
9803 strcpy(lastLoadGameTitle, title);
9804 lastLoadGameUseList = useList;
9808 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9809 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9810 lg->gameInfo.black);
9812 } else if (*title != NULLCHAR) {
9813 if (gameNumber > 1) {
9814 sprintf(buf, "%s %d", title, gameNumber);
9817 DisplayTitle(title);
9821 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9822 gameMode = PlayFromGameFile;
9826 currentMove = forwardMostMove = backwardMostMove = 0;
9827 CopyBoard(boards[0], initialPosition);
9831 * Skip the first gn-1 games in the file.
9832 * Also skip over anything that precedes an identifiable
9833 * start of game marker, to avoid being confused by
9834 * garbage at the start of the file. Currently
9835 * recognized start of game markers are the move number "1",
9836 * the pattern "gnuchess .* game", the pattern
9837 * "^[#;%] [^ ]* game file", and a PGN tag block.
9838 * A game that starts with one of the latter two patterns
9839 * will also have a move number 1, possibly
9840 * following a position diagram.
9841 * 5-4-02: Let's try being more lenient and allowing a game to
9842 * start with an unnumbered move. Does that break anything?
9844 cm = lastLoadGameStart = (ChessMove) 0;
9846 yyboardindex = forwardMostMove;
9847 cm = (ChessMove) yylex();
9850 if (cmailMsgLoaded) {
9851 nCmailGames = CMAIL_MAX_GAMES - gn;
9854 DisplayError(_("Game not found in file"), 0);
9861 lastLoadGameStart = cm;
9865 switch (lastLoadGameStart) {
9872 gn--; /* count this game */
9873 lastLoadGameStart = cm;
9882 switch (lastLoadGameStart) {
9887 gn--; /* count this game */
9888 lastLoadGameStart = cm;
9891 lastLoadGameStart = cm; /* game counted already */
9899 yyboardindex = forwardMostMove;
9900 cm = (ChessMove) yylex();
9901 } while (cm == PGNTag || cm == Comment);
9908 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9909 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9910 != CMAIL_OLD_RESULT) {
9912 cmailResult[ CMAIL_MAX_GAMES
9913 - gn - 1] = CMAIL_OLD_RESULT;
9919 /* Only a NormalMove can be at the start of a game
9920 * without a position diagram. */
9921 if (lastLoadGameStart == (ChessMove) 0) {
9923 lastLoadGameStart = MoveNumberOne;
9932 if (appData.debugMode)
9933 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9935 if (cm == XBoardGame) {
9936 /* Skip any header junk before position diagram and/or move 1 */
9938 yyboardindex = forwardMostMove;
9939 cm = (ChessMove) yylex();
9941 if (cm == (ChessMove) 0 ||
9942 cm == GNUChessGame || cm == XBoardGame) {
9943 /* Empty game; pretend end-of-file and handle later */
9948 if (cm == MoveNumberOne || cm == PositionDiagram ||
9949 cm == PGNTag || cm == Comment)
9952 } else if (cm == GNUChessGame) {
9953 if (gameInfo.event != NULL) {
9954 free(gameInfo.event);
9956 gameInfo.event = StrSave(yy_text);
9959 startedFromSetupPosition = FALSE;
9960 while (cm == PGNTag) {
9961 if (appData.debugMode)
9962 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9963 err = ParsePGNTag(yy_text, &gameInfo);
9964 if (!err) numPGNTags++;
9966 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9967 if(gameInfo.variant != oldVariant) {
9968 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9970 oldVariant = gameInfo.variant;
9971 if (appData.debugMode)
9972 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9976 if (gameInfo.fen != NULL) {
9977 Board initial_position;
9978 startedFromSetupPosition = TRUE;
9979 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9981 DisplayError(_("Bad FEN position in file"), 0);
9984 CopyBoard(boards[0], initial_position);
9985 if (blackPlaysFirst) {
9986 currentMove = forwardMostMove = backwardMostMove = 1;
9987 CopyBoard(boards[1], initial_position);
9988 strcpy(moveList[0], "");
9989 strcpy(parseList[0], "");
9990 timeRemaining[0][1] = whiteTimeRemaining;
9991 timeRemaining[1][1] = blackTimeRemaining;
9992 if (commentList[0] != NULL) {
9993 commentList[1] = commentList[0];
9994 commentList[0] = NULL;
9997 currentMove = forwardMostMove = backwardMostMove = 0;
9999 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10001 initialRulePlies = FENrulePlies;
10002 for( i=0; i< nrCastlingRights; i++ )
10003 initialRights[i] = initial_position[CASTLING][i];
10005 yyboardindex = forwardMostMove;
10006 free(gameInfo.fen);
10007 gameInfo.fen = NULL;
10010 yyboardindex = forwardMostMove;
10011 cm = (ChessMove) yylex();
10013 /* Handle comments interspersed among the tags */
10014 while (cm == Comment) {
10016 if (appData.debugMode)
10017 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10019 AppendComment(currentMove, p, FALSE);
10020 yyboardindex = forwardMostMove;
10021 cm = (ChessMove) yylex();
10025 /* don't rely on existence of Event tag since if game was
10026 * pasted from clipboard the Event tag may not exist
10028 if (numPGNTags > 0){
10030 if (gameInfo.variant == VariantNormal) {
10031 gameInfo.variant = StringToVariant(gameInfo.event);
10034 if( appData.autoDisplayTags ) {
10035 tags = PGNTags(&gameInfo);
10036 TagsPopUp(tags, CmailMsg());
10041 /* Make something up, but don't display it now */
10046 if (cm == PositionDiagram) {
10049 Board initial_position;
10051 if (appData.debugMode)
10052 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10054 if (!startedFromSetupPosition) {
10056 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10057 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10067 initial_position[i][j++] = CharToPiece(*p);
10070 while (*p == ' ' || *p == '\t' ||
10071 *p == '\n' || *p == '\r') p++;
10073 if (strncmp(p, "black", strlen("black"))==0)
10074 blackPlaysFirst = TRUE;
10076 blackPlaysFirst = FALSE;
10077 startedFromSetupPosition = TRUE;
10079 CopyBoard(boards[0], initial_position);
10080 if (blackPlaysFirst) {
10081 currentMove = forwardMostMove = backwardMostMove = 1;
10082 CopyBoard(boards[1], initial_position);
10083 strcpy(moveList[0], "");
10084 strcpy(parseList[0], "");
10085 timeRemaining[0][1] = whiteTimeRemaining;
10086 timeRemaining[1][1] = blackTimeRemaining;
10087 if (commentList[0] != NULL) {
10088 commentList[1] = commentList[0];
10089 commentList[0] = NULL;
10092 currentMove = forwardMostMove = backwardMostMove = 0;
10095 yyboardindex = forwardMostMove;
10096 cm = (ChessMove) yylex();
10099 if (first.pr == NoProc) {
10100 StartChessProgram(&first);
10102 InitChessProgram(&first, FALSE);
10103 SendToProgram("force\n", &first);
10104 if (startedFromSetupPosition) {
10105 SendBoard(&first, forwardMostMove);
10106 if (appData.debugMode) {
10107 fprintf(debugFP, "Load Game\n");
10109 DisplayBothClocks();
10112 /* [HGM] server: flag to write setup moves in broadcast file as one */
10113 loadFlag = appData.suppressLoadMoves;
10115 while (cm == Comment) {
10117 if (appData.debugMode)
10118 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10120 AppendComment(currentMove, p, FALSE);
10121 yyboardindex = forwardMostMove;
10122 cm = (ChessMove) yylex();
10125 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10126 cm == WhiteWins || cm == BlackWins ||
10127 cm == GameIsDrawn || cm == GameUnfinished) {
10128 DisplayMessage("", _("No moves in game"));
10129 if (cmailMsgLoaded) {
10130 if (appData.debugMode)
10131 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10135 DrawPosition(FALSE, boards[currentMove]);
10136 DisplayBothClocks();
10137 gameMode = EditGame;
10144 // [HGM] PV info: routine tests if comment empty
10145 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10146 DisplayComment(currentMove - 1, commentList[currentMove]);
10148 if (!matchMode && appData.timeDelay != 0)
10149 DrawPosition(FALSE, boards[currentMove]);
10151 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10152 programStats.ok_to_send = 1;
10155 /* if the first token after the PGN tags is a move
10156 * and not move number 1, retrieve it from the parser
10158 if (cm != MoveNumberOne)
10159 LoadGameOneMove(cm);
10161 /* load the remaining moves from the file */
10162 while (LoadGameOneMove((ChessMove)0)) {
10163 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10164 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10167 /* rewind to the start of the game */
10168 currentMove = backwardMostMove;
10170 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10172 if (oldGameMode == AnalyzeFile ||
10173 oldGameMode == AnalyzeMode) {
10174 AnalyzeFileEvent();
10177 if (matchMode || appData.timeDelay == 0) {
10179 gameMode = EditGame;
10181 } else if (appData.timeDelay > 0) {
10182 AutoPlayGameLoop();
10185 if (appData.debugMode)
10186 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10188 loadFlag = 0; /* [HGM] true game starts */
10192 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10194 ReloadPosition(offset)
10197 int positionNumber = lastLoadPositionNumber + offset;
10198 if (lastLoadPositionFP == NULL) {
10199 DisplayError(_("No position has been loaded yet"), 0);
10202 if (positionNumber <= 0) {
10203 DisplayError(_("Can't back up any further"), 0);
10206 return LoadPosition(lastLoadPositionFP, positionNumber,
10207 lastLoadPositionTitle);
10210 /* Load the nth position from the given file */
10212 LoadPositionFromFile(filename, n, title)
10220 if (strcmp(filename, "-") == 0) {
10221 return LoadPosition(stdin, n, "stdin");
10223 f = fopen(filename, "rb");
10225 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10226 DisplayError(buf, errno);
10229 return LoadPosition(f, n, title);
10234 /* Load the nth position from the given open file, and close it */
10236 LoadPosition(f, positionNumber, title)
10238 int positionNumber;
10241 char *p, line[MSG_SIZ];
10242 Board initial_position;
10243 int i, j, fenMode, pn;
10245 if (gameMode == Training )
10246 SetTrainingModeOff();
10248 if (gameMode != BeginningOfGame) {
10249 Reset(FALSE, TRUE);
10251 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10252 fclose(lastLoadPositionFP);
10254 if (positionNumber == 0) positionNumber = 1;
10255 lastLoadPositionFP = f;
10256 lastLoadPositionNumber = positionNumber;
10257 strcpy(lastLoadPositionTitle, title);
10258 if (first.pr == NoProc) {
10259 StartChessProgram(&first);
10260 InitChessProgram(&first, FALSE);
10262 pn = positionNumber;
10263 if (positionNumber < 0) {
10264 /* Negative position number means to seek to that byte offset */
10265 if (fseek(f, -positionNumber, 0) == -1) {
10266 DisplayError(_("Can't seek on position file"), 0);
10271 if (fseek(f, 0, 0) == -1) {
10272 if (f == lastLoadPositionFP ?
10273 positionNumber == lastLoadPositionNumber + 1 :
10274 positionNumber == 1) {
10277 DisplayError(_("Can't seek on position file"), 0);
10282 /* See if this file is FEN or old-style xboard */
10283 if (fgets(line, MSG_SIZ, f) == NULL) {
10284 DisplayError(_("Position not found in file"), 0);
10287 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10288 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10291 if (fenMode || line[0] == '#') pn--;
10293 /* skip positions before number pn */
10294 if (fgets(line, MSG_SIZ, f) == NULL) {
10296 DisplayError(_("Position not found in file"), 0);
10299 if (fenMode || line[0] == '#') pn--;
10304 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10305 DisplayError(_("Bad FEN position in file"), 0);
10309 (void) fgets(line, MSG_SIZ, f);
10310 (void) fgets(line, MSG_SIZ, f);
10312 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10313 (void) fgets(line, MSG_SIZ, f);
10314 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10317 initial_position[i][j++] = CharToPiece(*p);
10321 blackPlaysFirst = FALSE;
10323 (void) fgets(line, MSG_SIZ, f);
10324 if (strncmp(line, "black", strlen("black"))==0)
10325 blackPlaysFirst = TRUE;
10328 startedFromSetupPosition = TRUE;
10330 SendToProgram("force\n", &first);
10331 CopyBoard(boards[0], initial_position);
10332 if (blackPlaysFirst) {
10333 currentMove = forwardMostMove = backwardMostMove = 1;
10334 strcpy(moveList[0], "");
10335 strcpy(parseList[0], "");
10336 CopyBoard(boards[1], initial_position);
10337 DisplayMessage("", _("Black to play"));
10339 currentMove = forwardMostMove = backwardMostMove = 0;
10340 DisplayMessage("", _("White to play"));
10342 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10343 SendBoard(&first, forwardMostMove);
10344 if (appData.debugMode) {
10346 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10347 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10348 fprintf(debugFP, "Load Position\n");
10351 if (positionNumber > 1) {
10352 sprintf(line, "%s %d", title, positionNumber);
10353 DisplayTitle(line);
10355 DisplayTitle(title);
10357 gameMode = EditGame;
10360 timeRemaining[0][1] = whiteTimeRemaining;
10361 timeRemaining[1][1] = blackTimeRemaining;
10362 DrawPosition(FALSE, boards[currentMove]);
10369 CopyPlayerNameIntoFileName(dest, src)
10372 while (*src != NULLCHAR && *src != ',') {
10377 *(*dest)++ = *src++;
10382 char *DefaultFileName(ext)
10385 static char def[MSG_SIZ];
10388 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10390 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10392 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10401 /* Save the current game to the given file */
10403 SaveGameToFile(filename, append)
10410 if (strcmp(filename, "-") == 0) {
10411 return SaveGame(stdout, 0, NULL);
10413 f = fopen(filename, append ? "a" : "w");
10415 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10416 DisplayError(buf, errno);
10419 return SaveGame(f, 0, NULL);
10428 static char buf[MSG_SIZ];
10431 p = strchr(str, ' ');
10432 if (p == NULL) return str;
10433 strncpy(buf, str, p - str);
10434 buf[p - str] = NULLCHAR;
10438 #define PGN_MAX_LINE 75
10440 #define PGN_SIDE_WHITE 0
10441 #define PGN_SIDE_BLACK 1
10444 static int FindFirstMoveOutOfBook( int side )
10448 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10449 int index = backwardMostMove;
10450 int has_book_hit = 0;
10452 if( (index % 2) != side ) {
10456 while( index < forwardMostMove ) {
10457 /* Check to see if engine is in book */
10458 int depth = pvInfoList[index].depth;
10459 int score = pvInfoList[index].score;
10465 else if( score == 0 && depth == 63 ) {
10466 in_book = 1; /* Zappa */
10468 else if( score == 2 && depth == 99 ) {
10469 in_book = 1; /* Abrok */
10472 has_book_hit += in_book;
10488 void GetOutOfBookInfo( char * buf )
10492 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10494 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10495 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10499 if( oob[0] >= 0 || oob[1] >= 0 ) {
10500 for( i=0; i<2; i++ ) {
10504 if( i > 0 && oob[0] >= 0 ) {
10505 strcat( buf, " " );
10508 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10509 sprintf( buf+strlen(buf), "%s%.2f",
10510 pvInfoList[idx].score >= 0 ? "+" : "",
10511 pvInfoList[idx].score / 100.0 );
10517 /* Save game in PGN style and close the file */
10522 int i, offset, linelen, newblock;
10526 int movelen, numlen, blank;
10527 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10529 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10531 tm = time((time_t *) NULL);
10533 PrintPGNTags(f, &gameInfo);
10535 if (backwardMostMove > 0 || startedFromSetupPosition) {
10536 char *fen = PositionToFEN(backwardMostMove, NULL);
10537 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10538 fprintf(f, "\n{--------------\n");
10539 PrintPosition(f, backwardMostMove);
10540 fprintf(f, "--------------}\n");
10544 /* [AS] Out of book annotation */
10545 if( appData.saveOutOfBookInfo ) {
10548 GetOutOfBookInfo( buf );
10550 if( buf[0] != '\0' ) {
10551 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10558 i = backwardMostMove;
10562 while (i < forwardMostMove) {
10563 /* Print comments preceding this move */
10564 if (commentList[i] != NULL) {
10565 if (linelen > 0) fprintf(f, "\n");
10566 fprintf(f, "%s", commentList[i]);
10571 /* Format move number */
10572 if ((i % 2) == 0) {
10573 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10576 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10578 numtext[0] = NULLCHAR;
10581 numlen = strlen(numtext);
10584 /* Print move number */
10585 blank = linelen > 0 && numlen > 0;
10586 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10595 fprintf(f, "%s", numtext);
10599 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10600 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10603 blank = linelen > 0 && movelen > 0;
10604 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10613 fprintf(f, "%s", move_buffer);
10614 linelen += movelen;
10616 /* [AS] Add PV info if present */
10617 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10618 /* [HGM] add time */
10619 char buf[MSG_SIZ]; int seconds;
10621 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10623 if( seconds <= 0) buf[0] = 0; else
10624 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10625 seconds = (seconds + 4)/10; // round to full seconds
10626 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10627 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10630 sprintf( move_buffer, "{%s%.2f/%d%s}",
10631 pvInfoList[i].score >= 0 ? "+" : "",
10632 pvInfoList[i].score / 100.0,
10633 pvInfoList[i].depth,
10636 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10638 /* Print score/depth */
10639 blank = linelen > 0 && movelen > 0;
10640 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10649 fprintf(f, "%s", move_buffer);
10650 linelen += movelen;
10656 /* Start a new line */
10657 if (linelen > 0) fprintf(f, "\n");
10659 /* Print comments after last move */
10660 if (commentList[i] != NULL) {
10661 fprintf(f, "%s\n", commentList[i]);
10665 if (gameInfo.resultDetails != NULL &&
10666 gameInfo.resultDetails[0] != NULLCHAR) {
10667 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10668 PGNResult(gameInfo.result));
10670 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10674 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10678 /* Save game in old style and close the file */
10680 SaveGameOldStyle(f)
10686 tm = time((time_t *) NULL);
10688 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10691 if (backwardMostMove > 0 || startedFromSetupPosition) {
10692 fprintf(f, "\n[--------------\n");
10693 PrintPosition(f, backwardMostMove);
10694 fprintf(f, "--------------]\n");
10699 i = backwardMostMove;
10700 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10702 while (i < forwardMostMove) {
10703 if (commentList[i] != NULL) {
10704 fprintf(f, "[%s]\n", commentList[i]);
10707 if ((i % 2) == 1) {
10708 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10711 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10713 if (commentList[i] != NULL) {
10717 if (i >= forwardMostMove) {
10721 fprintf(f, "%s\n", parseList[i]);
10726 if (commentList[i] != NULL) {
10727 fprintf(f, "[%s]\n", commentList[i]);
10730 /* This isn't really the old style, but it's close enough */
10731 if (gameInfo.resultDetails != NULL &&
10732 gameInfo.resultDetails[0] != NULLCHAR) {
10733 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10734 gameInfo.resultDetails);
10736 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10743 /* Save the current game to open file f and close the file */
10745 SaveGame(f, dummy, dummy2)
10750 if (gameMode == EditPosition) EditPositionDone(TRUE);
10751 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10752 if (appData.oldSaveStyle)
10753 return SaveGameOldStyle(f);
10755 return SaveGamePGN(f);
10758 /* Save the current position to the given file */
10760 SavePositionToFile(filename)
10766 if (strcmp(filename, "-") == 0) {
10767 return SavePosition(stdout, 0, NULL);
10769 f = fopen(filename, "a");
10771 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10772 DisplayError(buf, errno);
10775 SavePosition(f, 0, NULL);
10781 /* Save the current position to the given open file and close the file */
10783 SavePosition(f, dummy, dummy2)
10791 if (gameMode == EditPosition) EditPositionDone(TRUE);
10792 if (appData.oldSaveStyle) {
10793 tm = time((time_t *) NULL);
10795 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10797 fprintf(f, "[--------------\n");
10798 PrintPosition(f, currentMove);
10799 fprintf(f, "--------------]\n");
10801 fen = PositionToFEN(currentMove, NULL);
10802 fprintf(f, "%s\n", fen);
10810 ReloadCmailMsgEvent(unregister)
10814 static char *inFilename = NULL;
10815 static char *outFilename;
10817 struct stat inbuf, outbuf;
10820 /* Any registered moves are unregistered if unregister is set, */
10821 /* i.e. invoked by the signal handler */
10823 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10824 cmailMoveRegistered[i] = FALSE;
10825 if (cmailCommentList[i] != NULL) {
10826 free(cmailCommentList[i]);
10827 cmailCommentList[i] = NULL;
10830 nCmailMovesRegistered = 0;
10833 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10834 cmailResult[i] = CMAIL_NOT_RESULT;
10838 if (inFilename == NULL) {
10839 /* Because the filenames are static they only get malloced once */
10840 /* and they never get freed */
10841 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10842 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10844 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10845 sprintf(outFilename, "%s.out", appData.cmailGameName);
10848 status = stat(outFilename, &outbuf);
10850 cmailMailedMove = FALSE;
10852 status = stat(inFilename, &inbuf);
10853 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10856 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10857 counts the games, notes how each one terminated, etc.
10859 It would be nice to remove this kludge and instead gather all
10860 the information while building the game list. (And to keep it
10861 in the game list nodes instead of having a bunch of fixed-size
10862 parallel arrays.) Note this will require getting each game's
10863 termination from the PGN tags, as the game list builder does
10864 not process the game moves. --mann
10866 cmailMsgLoaded = TRUE;
10867 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10869 /* Load first game in the file or popup game menu */
10870 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10872 #endif /* !WIN32 */
10880 char string[MSG_SIZ];
10882 if ( cmailMailedMove
10883 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10884 return TRUE; /* Allow free viewing */
10887 /* Unregister move to ensure that we don't leave RegisterMove */
10888 /* with the move registered when the conditions for registering no */
10890 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10891 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10892 nCmailMovesRegistered --;
10894 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10896 free(cmailCommentList[lastLoadGameNumber - 1]);
10897 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10901 if (cmailOldMove == -1) {
10902 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10906 if (currentMove > cmailOldMove + 1) {
10907 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10911 if (currentMove < cmailOldMove) {
10912 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10916 if (forwardMostMove > currentMove) {
10917 /* Silently truncate extra moves */
10921 if ( (currentMove == cmailOldMove + 1)
10922 || ( (currentMove == cmailOldMove)
10923 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10924 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10925 if (gameInfo.result != GameUnfinished) {
10926 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10929 if (commentList[currentMove] != NULL) {
10930 cmailCommentList[lastLoadGameNumber - 1]
10931 = StrSave(commentList[currentMove]);
10933 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10935 if (appData.debugMode)
10936 fprintf(debugFP, "Saving %s for game %d\n",
10937 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10940 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10942 f = fopen(string, "w");
10943 if (appData.oldSaveStyle) {
10944 SaveGameOldStyle(f); /* also closes the file */
10946 sprintf(string, "%s.pos.out", appData.cmailGameName);
10947 f = fopen(string, "w");
10948 SavePosition(f, 0, NULL); /* also closes the file */
10950 fprintf(f, "{--------------\n");
10951 PrintPosition(f, currentMove);
10952 fprintf(f, "--------------}\n\n");
10954 SaveGame(f, 0, NULL); /* also closes the file*/
10957 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10958 nCmailMovesRegistered ++;
10959 } else if (nCmailGames == 1) {
10960 DisplayError(_("You have not made a move yet"), 0);
10971 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10972 FILE *commandOutput;
10973 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10974 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10980 if (! cmailMsgLoaded) {
10981 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10985 if (nCmailGames == nCmailResults) {
10986 DisplayError(_("No unfinished games"), 0);
10990 #if CMAIL_PROHIBIT_REMAIL
10991 if (cmailMailedMove) {
10992 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);
10993 DisplayError(msg, 0);
10998 if (! (cmailMailedMove || RegisterMove())) return;
11000 if ( cmailMailedMove
11001 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11002 sprintf(string, partCommandString,
11003 appData.debugMode ? " -v" : "", appData.cmailGameName);
11004 commandOutput = popen(string, "r");
11006 if (commandOutput == NULL) {
11007 DisplayError(_("Failed to invoke cmail"), 0);
11009 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11010 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11012 if (nBuffers > 1) {
11013 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11014 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11015 nBytes = MSG_SIZ - 1;
11017 (void) memcpy(msg, buffer, nBytes);
11019 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11021 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11022 cmailMailedMove = TRUE; /* Prevent >1 moves */
11025 for (i = 0; i < nCmailGames; i ++) {
11026 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11031 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11033 sprintf(buffer, "%s/%s.%s.archive",
11035 appData.cmailGameName,
11037 LoadGameFromFile(buffer, 1, buffer, FALSE);
11038 cmailMsgLoaded = FALSE;
11042 DisplayInformation(msg);
11043 pclose(commandOutput);
11046 if ((*cmailMsg) != '\0') {
11047 DisplayInformation(cmailMsg);
11052 #endif /* !WIN32 */
11061 int prependComma = 0;
11063 char string[MSG_SIZ]; /* Space for game-list */
11066 if (!cmailMsgLoaded) return "";
11068 if (cmailMailedMove) {
11069 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11071 /* Create a list of games left */
11072 sprintf(string, "[");
11073 for (i = 0; i < nCmailGames; i ++) {
11074 if (! ( cmailMoveRegistered[i]
11075 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11076 if (prependComma) {
11077 sprintf(number, ",%d", i + 1);
11079 sprintf(number, "%d", i + 1);
11083 strcat(string, number);
11086 strcat(string, "]");
11088 if (nCmailMovesRegistered + nCmailResults == 0) {
11089 switch (nCmailGames) {
11092 _("Still need to make move for game\n"));
11097 _("Still need to make moves for both games\n"));
11102 _("Still need to make moves for all %d games\n"),
11107 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11110 _("Still need to make a move for game %s\n"),
11115 if (nCmailResults == nCmailGames) {
11116 sprintf(cmailMsg, _("No unfinished games\n"));
11118 sprintf(cmailMsg, _("Ready to send mail\n"));
11124 _("Still need to make moves for games %s\n"),
11136 if (gameMode == Training)
11137 SetTrainingModeOff();
11140 cmailMsgLoaded = FALSE;
11141 if (appData.icsActive) {
11142 SendToICS(ics_prefix);
11143 SendToICS("refresh\n");
11153 /* Give up on clean exit */
11157 /* Keep trying for clean exit */
11161 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11163 if (telnetISR != NULL) {
11164 RemoveInputSource(telnetISR);
11166 if (icsPR != NoProc) {
11167 DestroyChildProcess(icsPR, TRUE);
11170 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11171 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11173 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11174 /* make sure this other one finishes before killing it! */
11175 if(endingGame) { int count = 0;
11176 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11177 while(endingGame && count++ < 10) DoSleep(1);
11178 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11181 /* Kill off chess programs */
11182 if (first.pr != NoProc) {
11185 DoSleep( appData.delayBeforeQuit );
11186 SendToProgram("quit\n", &first);
11187 DoSleep( appData.delayAfterQuit );
11188 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11190 if (second.pr != NoProc) {
11191 DoSleep( appData.delayBeforeQuit );
11192 SendToProgram("quit\n", &second);
11193 DoSleep( appData.delayAfterQuit );
11194 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11196 if (first.isr != NULL) {
11197 RemoveInputSource(first.isr);
11199 if (second.isr != NULL) {
11200 RemoveInputSource(second.isr);
11203 ShutDownFrontEnd();
11210 if (appData.debugMode)
11211 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11215 if (gameMode == MachinePlaysWhite ||
11216 gameMode == MachinePlaysBlack) {
11219 DisplayBothClocks();
11221 if (gameMode == PlayFromGameFile) {
11222 if (appData.timeDelay >= 0)
11223 AutoPlayGameLoop();
11224 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11225 Reset(FALSE, TRUE);
11226 SendToICS(ics_prefix);
11227 SendToICS("refresh\n");
11228 } else if (currentMove < forwardMostMove) {
11229 ForwardInner(forwardMostMove);
11231 pauseExamInvalid = FALSE;
11233 switch (gameMode) {
11237 pauseExamForwardMostMove = forwardMostMove;
11238 pauseExamInvalid = FALSE;
11241 case IcsPlayingWhite:
11242 case IcsPlayingBlack:
11246 case PlayFromGameFile:
11247 (void) StopLoadGameTimer();
11251 case BeginningOfGame:
11252 if (appData.icsActive) return;
11253 /* else fall through */
11254 case MachinePlaysWhite:
11255 case MachinePlaysBlack:
11256 case TwoMachinesPlay:
11257 if (forwardMostMove == 0)
11258 return; /* don't pause if no one has moved */
11259 if ((gameMode == MachinePlaysWhite &&
11260 !WhiteOnMove(forwardMostMove)) ||
11261 (gameMode == MachinePlaysBlack &&
11262 WhiteOnMove(forwardMostMove))) {
11275 char title[MSG_SIZ];
11277 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11278 strcpy(title, _("Edit comment"));
11280 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11281 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11282 parseList[currentMove - 1]);
11285 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11292 char *tags = PGNTags(&gameInfo);
11293 EditTagsPopUp(tags);
11300 if (appData.noChessProgram || gameMode == AnalyzeMode)
11303 if (gameMode != AnalyzeFile) {
11304 if (!appData.icsEngineAnalyze) {
11306 if (gameMode != EditGame) return;
11308 ResurrectChessProgram();
11309 SendToProgram("analyze\n", &first);
11310 first.analyzing = TRUE;
11311 /*first.maybeThinking = TRUE;*/
11312 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11313 EngineOutputPopUp();
11315 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11320 StartAnalysisClock();
11321 GetTimeMark(&lastNodeCountTime);
11328 if (appData.noChessProgram || gameMode == AnalyzeFile)
11331 if (gameMode != AnalyzeMode) {
11333 if (gameMode != EditGame) return;
11334 ResurrectChessProgram();
11335 SendToProgram("analyze\n", &first);
11336 first.analyzing = TRUE;
11337 /*first.maybeThinking = TRUE;*/
11338 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11339 EngineOutputPopUp();
11341 gameMode = AnalyzeFile;
11346 StartAnalysisClock();
11347 GetTimeMark(&lastNodeCountTime);
11352 MachineWhiteEvent()
11355 char *bookHit = NULL;
11357 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11361 if (gameMode == PlayFromGameFile ||
11362 gameMode == TwoMachinesPlay ||
11363 gameMode == Training ||
11364 gameMode == AnalyzeMode ||
11365 gameMode == EndOfGame)
11368 if (gameMode == EditPosition)
11369 EditPositionDone(TRUE);
11371 if (!WhiteOnMove(currentMove)) {
11372 DisplayError(_("It is not White's turn"), 0);
11376 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11379 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11380 gameMode == AnalyzeFile)
11383 ResurrectChessProgram(); /* in case it isn't running */
11384 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11385 gameMode = MachinePlaysWhite;
11388 gameMode = MachinePlaysWhite;
11392 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11394 if (first.sendName) {
11395 sprintf(buf, "name %s\n", gameInfo.black);
11396 SendToProgram(buf, &first);
11398 if (first.sendTime) {
11399 if (first.useColors) {
11400 SendToProgram("black\n", &first); /*gnu kludge*/
11402 SendTimeRemaining(&first, TRUE);
11404 if (first.useColors) {
11405 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11407 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11408 SetMachineThinkingEnables();
11409 first.maybeThinking = TRUE;
11413 if (appData.autoFlipView && !flipView) {
11414 flipView = !flipView;
11415 DrawPosition(FALSE, NULL);
11416 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11419 if(bookHit) { // [HGM] book: simulate book reply
11420 static char bookMove[MSG_SIZ]; // a bit generous?
11422 programStats.nodes = programStats.depth = programStats.time =
11423 programStats.score = programStats.got_only_move = 0;
11424 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11426 strcpy(bookMove, "move ");
11427 strcat(bookMove, bookHit);
11428 HandleMachineMove(bookMove, &first);
11433 MachineBlackEvent()
11436 char *bookHit = NULL;
11438 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11442 if (gameMode == PlayFromGameFile ||
11443 gameMode == TwoMachinesPlay ||
11444 gameMode == Training ||
11445 gameMode == AnalyzeMode ||
11446 gameMode == EndOfGame)
11449 if (gameMode == EditPosition)
11450 EditPositionDone(TRUE);
11452 if (WhiteOnMove(currentMove)) {
11453 DisplayError(_("It is not Black's turn"), 0);
11457 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11460 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11461 gameMode == AnalyzeFile)
11464 ResurrectChessProgram(); /* in case it isn't running */
11465 gameMode = MachinePlaysBlack;
11469 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11471 if (first.sendName) {
11472 sprintf(buf, "name %s\n", gameInfo.white);
11473 SendToProgram(buf, &first);
11475 if (first.sendTime) {
11476 if (first.useColors) {
11477 SendToProgram("white\n", &first); /*gnu kludge*/
11479 SendTimeRemaining(&first, FALSE);
11481 if (first.useColors) {
11482 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11484 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11485 SetMachineThinkingEnables();
11486 first.maybeThinking = TRUE;
11489 if (appData.autoFlipView && flipView) {
11490 flipView = !flipView;
11491 DrawPosition(FALSE, NULL);
11492 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11494 if(bookHit) { // [HGM] book: simulate book reply
11495 static char bookMove[MSG_SIZ]; // a bit generous?
11497 programStats.nodes = programStats.depth = programStats.time =
11498 programStats.score = programStats.got_only_move = 0;
11499 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11501 strcpy(bookMove, "move ");
11502 strcat(bookMove, bookHit);
11503 HandleMachineMove(bookMove, &first);
11509 DisplayTwoMachinesTitle()
11512 if (appData.matchGames > 0) {
11513 if (first.twoMachinesColor[0] == 'w') {
11514 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11515 gameInfo.white, gameInfo.black,
11516 first.matchWins, second.matchWins,
11517 matchGame - 1 - (first.matchWins + second.matchWins));
11519 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11520 gameInfo.white, gameInfo.black,
11521 second.matchWins, first.matchWins,
11522 matchGame - 1 - (first.matchWins + second.matchWins));
11525 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11531 TwoMachinesEvent P((void))
11535 ChessProgramState *onmove;
11536 char *bookHit = NULL;
11538 if (appData.noChessProgram) return;
11540 switch (gameMode) {
11541 case TwoMachinesPlay:
11543 case MachinePlaysWhite:
11544 case MachinePlaysBlack:
11545 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11546 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11550 case BeginningOfGame:
11551 case PlayFromGameFile:
11554 if (gameMode != EditGame) return;
11557 EditPositionDone(TRUE);
11568 // forwardMostMove = currentMove;
11569 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11570 ResurrectChessProgram(); /* in case first program isn't running */
11572 if (second.pr == NULL) {
11573 StartChessProgram(&second);
11574 if (second.protocolVersion == 1) {
11575 TwoMachinesEventIfReady();
11577 /* kludge: allow timeout for initial "feature" command */
11579 DisplayMessage("", _("Starting second chess program"));
11580 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11584 DisplayMessage("", "");
11585 InitChessProgram(&second, FALSE);
11586 SendToProgram("force\n", &second);
11587 if (startedFromSetupPosition) {
11588 SendBoard(&second, backwardMostMove);
11589 if (appData.debugMode) {
11590 fprintf(debugFP, "Two Machines\n");
11593 for (i = backwardMostMove; i < forwardMostMove; i++) {
11594 SendMoveToProgram(i, &second);
11597 gameMode = TwoMachinesPlay;
11601 DisplayTwoMachinesTitle();
11603 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11609 SendToProgram(first.computerString, &first);
11610 if (first.sendName) {
11611 sprintf(buf, "name %s\n", second.tidy);
11612 SendToProgram(buf, &first);
11614 SendToProgram(second.computerString, &second);
11615 if (second.sendName) {
11616 sprintf(buf, "name %s\n", first.tidy);
11617 SendToProgram(buf, &second);
11621 if (!first.sendTime || !second.sendTime) {
11622 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11623 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11625 if (onmove->sendTime) {
11626 if (onmove->useColors) {
11627 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11629 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11631 if (onmove->useColors) {
11632 SendToProgram(onmove->twoMachinesColor, onmove);
11634 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11635 // SendToProgram("go\n", onmove);
11636 onmove->maybeThinking = TRUE;
11637 SetMachineThinkingEnables();
11641 if(bookHit) { // [HGM] book: simulate book reply
11642 static char bookMove[MSG_SIZ]; // a bit generous?
11644 programStats.nodes = programStats.depth = programStats.time =
11645 programStats.score = programStats.got_only_move = 0;
11646 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11648 strcpy(bookMove, "move ");
11649 strcat(bookMove, bookHit);
11650 savedMessage = bookMove; // args for deferred call
11651 savedState = onmove;
11652 ScheduleDelayedEvent(DeferredBookMove, 1);
11659 if (gameMode == Training) {
11660 SetTrainingModeOff();
11661 gameMode = PlayFromGameFile;
11662 DisplayMessage("", _("Training mode off"));
11664 gameMode = Training;
11665 animateTraining = appData.animate;
11667 /* make sure we are not already at the end of the game */
11668 if (currentMove < forwardMostMove) {
11669 SetTrainingModeOn();
11670 DisplayMessage("", _("Training mode on"));
11672 gameMode = PlayFromGameFile;
11673 DisplayError(_("Already at end of game"), 0);
11682 if (!appData.icsActive) return;
11683 switch (gameMode) {
11684 case IcsPlayingWhite:
11685 case IcsPlayingBlack:
11688 case BeginningOfGame:
11696 EditPositionDone(TRUE);
11709 gameMode = IcsIdle;
11720 switch (gameMode) {
11722 SetTrainingModeOff();
11724 case MachinePlaysWhite:
11725 case MachinePlaysBlack:
11726 case BeginningOfGame:
11727 SendToProgram("force\n", &first);
11728 SetUserThinkingEnables();
11730 case PlayFromGameFile:
11731 (void) StopLoadGameTimer();
11732 if (gameFileFP != NULL) {
11737 EditPositionDone(TRUE);
11742 SendToProgram("force\n", &first);
11744 case TwoMachinesPlay:
11745 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11746 ResurrectChessProgram();
11747 SetUserThinkingEnables();
11750 ResurrectChessProgram();
11752 case IcsPlayingBlack:
11753 case IcsPlayingWhite:
11754 DisplayError(_("Warning: You are still playing a game"), 0);
11757 DisplayError(_("Warning: You are still observing a game"), 0);
11760 DisplayError(_("Warning: You are still examining a game"), 0);
11771 first.offeredDraw = second.offeredDraw = 0;
11773 if (gameMode == PlayFromGameFile) {
11774 whiteTimeRemaining = timeRemaining[0][currentMove];
11775 blackTimeRemaining = timeRemaining[1][currentMove];
11779 if (gameMode == MachinePlaysWhite ||
11780 gameMode == MachinePlaysBlack ||
11781 gameMode == TwoMachinesPlay ||
11782 gameMode == EndOfGame) {
11783 i = forwardMostMove;
11784 while (i > currentMove) {
11785 SendToProgram("undo\n", &first);
11788 whiteTimeRemaining = timeRemaining[0][currentMove];
11789 blackTimeRemaining = timeRemaining[1][currentMove];
11790 DisplayBothClocks();
11791 if (whiteFlag || blackFlag) {
11792 whiteFlag = blackFlag = 0;
11797 gameMode = EditGame;
11804 EditPositionEvent()
11806 if (gameMode == EditPosition) {
11812 if (gameMode != EditGame) return;
11814 gameMode = EditPosition;
11817 if (currentMove > 0)
11818 CopyBoard(boards[0], boards[currentMove]);
11820 blackPlaysFirst = !WhiteOnMove(currentMove);
11822 currentMove = forwardMostMove = backwardMostMove = 0;
11823 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11830 /* [DM] icsEngineAnalyze - possible call from other functions */
11831 if (appData.icsEngineAnalyze) {
11832 appData.icsEngineAnalyze = FALSE;
11834 DisplayMessage("",_("Close ICS engine analyze..."));
11836 if (first.analysisSupport && first.analyzing) {
11837 SendToProgram("exit\n", &first);
11838 first.analyzing = FALSE;
11840 thinkOutput[0] = NULLCHAR;
11844 EditPositionDone(Boolean fakeRights)
11846 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11848 startedFromSetupPosition = TRUE;
11849 InitChessProgram(&first, FALSE);
11850 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11851 boards[0][EP_STATUS] = EP_NONE;
11852 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11853 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11854 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11855 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11856 } else boards[0][CASTLING][2] = NoRights;
11857 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11858 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11859 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11860 } else boards[0][CASTLING][5] = NoRights;
11862 SendToProgram("force\n", &first);
11863 if (blackPlaysFirst) {
11864 strcpy(moveList[0], "");
11865 strcpy(parseList[0], "");
11866 currentMove = forwardMostMove = backwardMostMove = 1;
11867 CopyBoard(boards[1], boards[0]);
11869 currentMove = forwardMostMove = backwardMostMove = 0;
11871 SendBoard(&first, forwardMostMove);
11872 if (appData.debugMode) {
11873 fprintf(debugFP, "EditPosDone\n");
11876 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11877 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11878 gameMode = EditGame;
11880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11881 ClearHighlights(); /* [AS] */
11884 /* Pause for `ms' milliseconds */
11885 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11895 } while (SubtractTimeMarks(&m2, &m1) < ms);
11898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11900 SendMultiLineToICS(buf)
11903 char temp[MSG_SIZ+1], *p;
11910 strncpy(temp, buf, len);
11915 if (*p == '\n' || *p == '\r')
11920 strcat(temp, "\n");
11922 SendToPlayer(temp, strlen(temp));
11926 SetWhiteToPlayEvent()
11928 if (gameMode == EditPosition) {
11929 blackPlaysFirst = FALSE;
11930 DisplayBothClocks(); /* works because currentMove is 0 */
11931 } else if (gameMode == IcsExamining) {
11932 SendToICS(ics_prefix);
11933 SendToICS("tomove white\n");
11938 SetBlackToPlayEvent()
11940 if (gameMode == EditPosition) {
11941 blackPlaysFirst = TRUE;
11942 currentMove = 1; /* kludge */
11943 DisplayBothClocks();
11945 } else if (gameMode == IcsExamining) {
11946 SendToICS(ics_prefix);
11947 SendToICS("tomove black\n");
11952 EditPositionMenuEvent(selection, x, y)
11953 ChessSquare selection;
11957 ChessSquare piece = boards[0][y][x];
11959 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11961 switch (selection) {
11963 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11964 SendToICS(ics_prefix);
11965 SendToICS("bsetup clear\n");
11966 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11967 SendToICS(ics_prefix);
11968 SendToICS("clearboard\n");
11970 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11971 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11972 for (y = 0; y < BOARD_HEIGHT; y++) {
11973 if (gameMode == IcsExamining) {
11974 if (boards[currentMove][y][x] != EmptySquare) {
11975 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11980 boards[0][y][x] = p;
11985 if (gameMode == EditPosition) {
11986 DrawPosition(FALSE, boards[0]);
11991 SetWhiteToPlayEvent();
11995 SetBlackToPlayEvent();
11999 if (gameMode == IcsExamining) {
12000 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12001 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12004 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12005 if(x == BOARD_LEFT-2) {
12006 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12007 boards[0][y][1] = 0;
12009 if(x == BOARD_RGHT+1) {
12010 if(y >= gameInfo.holdingsSize) break;
12011 boards[0][y][BOARD_WIDTH-2] = 0;
12014 boards[0][y][x] = EmptySquare;
12015 DrawPosition(FALSE, boards[0]);
12020 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12021 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12022 selection = (ChessSquare) (PROMOTED piece);
12023 } else if(piece == EmptySquare) selection = WhiteSilver;
12024 else selection = (ChessSquare)((int)piece - 1);
12028 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12029 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12030 selection = (ChessSquare) (DEMOTED piece);
12031 } else if(piece == EmptySquare) selection = BlackSilver;
12032 else selection = (ChessSquare)((int)piece + 1);
12037 if(gameInfo.variant == VariantShatranj ||
12038 gameInfo.variant == VariantXiangqi ||
12039 gameInfo.variant == VariantCourier ||
12040 gameInfo.variant == VariantMakruk )
12041 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12046 if(gameInfo.variant == VariantXiangqi)
12047 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12048 if(gameInfo.variant == VariantKnightmate)
12049 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12052 if (gameMode == IcsExamining) {
12053 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12054 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12055 PieceToChar(selection), AAA + x, ONE + y);
12058 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12060 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12061 n = PieceToNumber(selection - BlackPawn);
12062 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12063 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12064 boards[0][BOARD_HEIGHT-1-n][1]++;
12066 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12067 n = PieceToNumber(selection);
12068 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12069 boards[0][n][BOARD_WIDTH-1] = selection;
12070 boards[0][n][BOARD_WIDTH-2]++;
12073 boards[0][y][x] = selection;
12074 DrawPosition(TRUE, boards[0]);
12082 DropMenuEvent(selection, x, y)
12083 ChessSquare selection;
12086 ChessMove moveType;
12088 switch (gameMode) {
12089 case IcsPlayingWhite:
12090 case MachinePlaysBlack:
12091 if (!WhiteOnMove(currentMove)) {
12092 DisplayMoveError(_("It is Black's turn"));
12095 moveType = WhiteDrop;
12097 case IcsPlayingBlack:
12098 case MachinePlaysWhite:
12099 if (WhiteOnMove(currentMove)) {
12100 DisplayMoveError(_("It is White's turn"));
12103 moveType = BlackDrop;
12106 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12112 if (moveType == BlackDrop && selection < BlackPawn) {
12113 selection = (ChessSquare) ((int) selection
12114 + (int) BlackPawn - (int) WhitePawn);
12116 if (boards[currentMove][y][x] != EmptySquare) {
12117 DisplayMoveError(_("That square is occupied"));
12121 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12127 /* Accept a pending offer of any kind from opponent */
12129 if (appData.icsActive) {
12130 SendToICS(ics_prefix);
12131 SendToICS("accept\n");
12132 } else if (cmailMsgLoaded) {
12133 if (currentMove == cmailOldMove &&
12134 commentList[cmailOldMove] != NULL &&
12135 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12136 "Black offers a draw" : "White offers a draw")) {
12138 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12139 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12141 DisplayError(_("There is no pending offer on this move"), 0);
12142 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12145 /* Not used for offers from chess program */
12152 /* Decline a pending offer of any kind from opponent */
12154 if (appData.icsActive) {
12155 SendToICS(ics_prefix);
12156 SendToICS("decline\n");
12157 } else if (cmailMsgLoaded) {
12158 if (currentMove == cmailOldMove &&
12159 commentList[cmailOldMove] != NULL &&
12160 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12161 "Black offers a draw" : "White offers a draw")) {
12163 AppendComment(cmailOldMove, "Draw declined", TRUE);
12164 DisplayComment(cmailOldMove - 1, "Draw declined");
12167 DisplayError(_("There is no pending offer on this move"), 0);
12170 /* Not used for offers from chess program */
12177 /* Issue ICS rematch command */
12178 if (appData.icsActive) {
12179 SendToICS(ics_prefix);
12180 SendToICS("rematch\n");
12187 /* Call your opponent's flag (claim a win on time) */
12188 if (appData.icsActive) {
12189 SendToICS(ics_prefix);
12190 SendToICS("flag\n");
12192 switch (gameMode) {
12195 case MachinePlaysWhite:
12198 GameEnds(GameIsDrawn, "Both players ran out of time",
12201 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12203 DisplayError(_("Your opponent is not out of time"), 0);
12206 case MachinePlaysBlack:
12209 GameEnds(GameIsDrawn, "Both players ran out of time",
12212 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12214 DisplayError(_("Your opponent is not out of time"), 0);
12224 /* Offer draw or accept pending draw offer from opponent */
12226 if (appData.icsActive) {
12227 /* Note: tournament rules require draw offers to be
12228 made after you make your move but before you punch
12229 your clock. Currently ICS doesn't let you do that;
12230 instead, you immediately punch your clock after making
12231 a move, but you can offer a draw at any time. */
12233 SendToICS(ics_prefix);
12234 SendToICS("draw\n");
12235 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12236 } else if (cmailMsgLoaded) {
12237 if (currentMove == cmailOldMove &&
12238 commentList[cmailOldMove] != NULL &&
12239 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12240 "Black offers a draw" : "White offers a draw")) {
12241 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12242 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12243 } else if (currentMove == cmailOldMove + 1) {
12244 char *offer = WhiteOnMove(cmailOldMove) ?
12245 "White offers a draw" : "Black offers a draw";
12246 AppendComment(currentMove, offer, TRUE);
12247 DisplayComment(currentMove - 1, offer);
12248 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12250 DisplayError(_("You must make your move before offering a draw"), 0);
12251 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12253 } else if (first.offeredDraw) {
12254 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12256 if (first.sendDrawOffers) {
12257 SendToProgram("draw\n", &first);
12258 userOfferedDraw = TRUE;
12266 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12268 if (appData.icsActive) {
12269 SendToICS(ics_prefix);
12270 SendToICS("adjourn\n");
12272 /* Currently GNU Chess doesn't offer or accept Adjourns */
12280 /* Offer Abort or accept pending Abort offer from opponent */
12282 if (appData.icsActive) {
12283 SendToICS(ics_prefix);
12284 SendToICS("abort\n");
12286 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12293 /* Resign. You can do this even if it's not your turn. */
12295 if (appData.icsActive) {
12296 SendToICS(ics_prefix);
12297 SendToICS("resign\n");
12299 switch (gameMode) {
12300 case MachinePlaysWhite:
12301 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12303 case MachinePlaysBlack:
12304 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12307 if (cmailMsgLoaded) {
12309 if (WhiteOnMove(cmailOldMove)) {
12310 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12312 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12314 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12325 StopObservingEvent()
12327 /* Stop observing current games */
12328 SendToICS(ics_prefix);
12329 SendToICS("unobserve\n");
12333 StopExaminingEvent()
12335 /* Stop observing current game */
12336 SendToICS(ics_prefix);
12337 SendToICS("unexamine\n");
12341 ForwardInner(target)
12346 if (appData.debugMode)
12347 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12348 target, currentMove, forwardMostMove);
12350 if (gameMode == EditPosition)
12353 if (gameMode == PlayFromGameFile && !pausing)
12356 if (gameMode == IcsExamining && pausing)
12357 limit = pauseExamForwardMostMove;
12359 limit = forwardMostMove;
12361 if (target > limit) target = limit;
12363 if (target > 0 && moveList[target - 1][0]) {
12364 int fromX, fromY, toX, toY;
12365 toX = moveList[target - 1][2] - AAA;
12366 toY = moveList[target - 1][3] - ONE;
12367 if (moveList[target - 1][1] == '@') {
12368 if (appData.highlightLastMove) {
12369 SetHighlights(-1, -1, toX, toY);
12372 fromX = moveList[target - 1][0] - AAA;
12373 fromY = moveList[target - 1][1] - ONE;
12374 if (target == currentMove + 1) {
12375 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12377 if (appData.highlightLastMove) {
12378 SetHighlights(fromX, fromY, toX, toY);
12382 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12383 gameMode == Training || gameMode == PlayFromGameFile ||
12384 gameMode == AnalyzeFile) {
12385 while (currentMove < target) {
12386 SendMoveToProgram(currentMove++, &first);
12389 currentMove = target;
12392 if (gameMode == EditGame || gameMode == EndOfGame) {
12393 whiteTimeRemaining = timeRemaining[0][currentMove];
12394 blackTimeRemaining = timeRemaining[1][currentMove];
12396 DisplayBothClocks();
12397 DisplayMove(currentMove - 1);
12398 DrawPosition(FALSE, boards[currentMove]);
12399 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12400 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12401 DisplayComment(currentMove - 1, commentList[currentMove]);
12409 if (gameMode == IcsExamining && !pausing) {
12410 SendToICS(ics_prefix);
12411 SendToICS("forward\n");
12413 ForwardInner(currentMove + 1);
12420 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12421 /* to optimze, we temporarily turn off analysis mode while we feed
12422 * the remaining moves to the engine. Otherwise we get analysis output
12425 if (first.analysisSupport) {
12426 SendToProgram("exit\nforce\n", &first);
12427 first.analyzing = FALSE;
12431 if (gameMode == IcsExamining && !pausing) {
12432 SendToICS(ics_prefix);
12433 SendToICS("forward 999999\n");
12435 ForwardInner(forwardMostMove);
12438 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12439 /* we have fed all the moves, so reactivate analysis mode */
12440 SendToProgram("analyze\n", &first);
12441 first.analyzing = TRUE;
12442 /*first.maybeThinking = TRUE;*/
12443 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12448 BackwardInner(target)
12451 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12453 if (appData.debugMode)
12454 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12455 target, currentMove, forwardMostMove);
12457 if (gameMode == EditPosition) return;
12458 if (currentMove <= backwardMostMove) {
12460 DrawPosition(full_redraw, boards[currentMove]);
12463 if (gameMode == PlayFromGameFile && !pausing)
12466 if (moveList[target][0]) {
12467 int fromX, fromY, toX, toY;
12468 toX = moveList[target][2] - AAA;
12469 toY = moveList[target][3] - ONE;
12470 if (moveList[target][1] == '@') {
12471 if (appData.highlightLastMove) {
12472 SetHighlights(-1, -1, toX, toY);
12475 fromX = moveList[target][0] - AAA;
12476 fromY = moveList[target][1] - ONE;
12477 if (target == currentMove - 1) {
12478 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12480 if (appData.highlightLastMove) {
12481 SetHighlights(fromX, fromY, toX, toY);
12485 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12486 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12487 while (currentMove > target) {
12488 SendToProgram("undo\n", &first);
12492 currentMove = target;
12495 if (gameMode == EditGame || gameMode == EndOfGame) {
12496 whiteTimeRemaining = timeRemaining[0][currentMove];
12497 blackTimeRemaining = timeRemaining[1][currentMove];
12499 DisplayBothClocks();
12500 DisplayMove(currentMove - 1);
12501 DrawPosition(full_redraw, boards[currentMove]);
12502 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12503 // [HGM] PV info: routine tests if comment empty
12504 DisplayComment(currentMove - 1, commentList[currentMove]);
12510 if (gameMode == IcsExamining && !pausing) {
12511 SendToICS(ics_prefix);
12512 SendToICS("backward\n");
12514 BackwardInner(currentMove - 1);
12521 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12522 /* to optimize, we temporarily turn off analysis mode while we undo
12523 * all the moves. Otherwise we get analysis output after each undo.
12525 if (first.analysisSupport) {
12526 SendToProgram("exit\nforce\n", &first);
12527 first.analyzing = FALSE;
12531 if (gameMode == IcsExamining && !pausing) {
12532 SendToICS(ics_prefix);
12533 SendToICS("backward 999999\n");
12535 BackwardInner(backwardMostMove);
12538 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12539 /* we have fed all the moves, so reactivate analysis mode */
12540 SendToProgram("analyze\n", &first);
12541 first.analyzing = TRUE;
12542 /*first.maybeThinking = TRUE;*/
12543 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12550 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12551 if (to >= forwardMostMove) to = forwardMostMove;
12552 if (to <= backwardMostMove) to = backwardMostMove;
12553 if (to < currentMove) {
12563 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12566 if (gameMode != IcsExamining) {
12567 DisplayError(_("You are not examining a game"), 0);
12571 DisplayError(_("You can't revert while pausing"), 0);
12574 SendToICS(ics_prefix);
12575 SendToICS("revert\n");
12581 switch (gameMode) {
12582 case MachinePlaysWhite:
12583 case MachinePlaysBlack:
12584 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12585 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12588 if (forwardMostMove < 2) return;
12589 currentMove = forwardMostMove = forwardMostMove - 2;
12590 whiteTimeRemaining = timeRemaining[0][currentMove];
12591 blackTimeRemaining = timeRemaining[1][currentMove];
12592 DisplayBothClocks();
12593 DisplayMove(currentMove - 1);
12594 ClearHighlights();/*!! could figure this out*/
12595 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12596 SendToProgram("remove\n", &first);
12597 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12600 case BeginningOfGame:
12604 case IcsPlayingWhite:
12605 case IcsPlayingBlack:
12606 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12607 SendToICS(ics_prefix);
12608 SendToICS("takeback 2\n");
12610 SendToICS(ics_prefix);
12611 SendToICS("takeback 1\n");
12620 ChessProgramState *cps;
12622 switch (gameMode) {
12623 case MachinePlaysWhite:
12624 if (!WhiteOnMove(forwardMostMove)) {
12625 DisplayError(_("It is your turn"), 0);
12630 case MachinePlaysBlack:
12631 if (WhiteOnMove(forwardMostMove)) {
12632 DisplayError(_("It is your turn"), 0);
12637 case TwoMachinesPlay:
12638 if (WhiteOnMove(forwardMostMove) ==
12639 (first.twoMachinesColor[0] == 'w')) {
12645 case BeginningOfGame:
12649 SendToProgram("?\n", cps);
12653 TruncateGameEvent()
12656 if (gameMode != EditGame) return;
12663 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12664 if (forwardMostMove > currentMove) {
12665 if (gameInfo.resultDetails != NULL) {
12666 free(gameInfo.resultDetails);
12667 gameInfo.resultDetails = NULL;
12668 gameInfo.result = GameUnfinished;
12670 forwardMostMove = currentMove;
12671 HistorySet(parseList, backwardMostMove, forwardMostMove,
12679 if (appData.noChessProgram) return;
12680 switch (gameMode) {
12681 case MachinePlaysWhite:
12682 if (WhiteOnMove(forwardMostMove)) {
12683 DisplayError(_("Wait until your turn"), 0);
12687 case BeginningOfGame:
12688 case MachinePlaysBlack:
12689 if (!WhiteOnMove(forwardMostMove)) {
12690 DisplayError(_("Wait until your turn"), 0);
12695 DisplayError(_("No hint available"), 0);
12698 SendToProgram("hint\n", &first);
12699 hintRequested = TRUE;
12705 if (appData.noChessProgram) return;
12706 switch (gameMode) {
12707 case MachinePlaysWhite:
12708 if (WhiteOnMove(forwardMostMove)) {
12709 DisplayError(_("Wait until your turn"), 0);
12713 case BeginningOfGame:
12714 case MachinePlaysBlack:
12715 if (!WhiteOnMove(forwardMostMove)) {
12716 DisplayError(_("Wait until your turn"), 0);
12721 EditPositionDone(TRUE);
12723 case TwoMachinesPlay:
12728 SendToProgram("bk\n", &first);
12729 bookOutput[0] = NULLCHAR;
12730 bookRequested = TRUE;
12736 char *tags = PGNTags(&gameInfo);
12737 TagsPopUp(tags, CmailMsg());
12741 /* end button procedures */
12744 PrintPosition(fp, move)
12750 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12751 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12752 char c = PieceToChar(boards[move][i][j]);
12753 fputc(c == 'x' ? '.' : c, fp);
12754 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12757 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12758 fprintf(fp, "white to play\n");
12760 fprintf(fp, "black to play\n");
12767 if (gameInfo.white != NULL) {
12768 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12774 /* Find last component of program's own name, using some heuristics */
12776 TidyProgramName(prog, host, buf)
12777 char *prog, *host, buf[MSG_SIZ];
12780 int local = (strcmp(host, "localhost") == 0);
12781 while (!local && (p = strchr(prog, ';')) != NULL) {
12783 while (*p == ' ') p++;
12786 if (*prog == '"' || *prog == '\'') {
12787 q = strchr(prog + 1, *prog);
12789 q = strchr(prog, ' ');
12791 if (q == NULL) q = prog + strlen(prog);
12793 while (p >= prog && *p != '/' && *p != '\\') p--;
12795 if(p == prog && *p == '"') p++;
12796 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12797 memcpy(buf, p, q - p);
12798 buf[q - p] = NULLCHAR;
12806 TimeControlTagValue()
12809 if (!appData.clockMode) {
12811 } else if (movesPerSession > 0) {
12812 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12813 } else if (timeIncrement == 0) {
12814 sprintf(buf, "%ld", timeControl/1000);
12816 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12818 return StrSave(buf);
12824 /* This routine is used only for certain modes */
12825 VariantClass v = gameInfo.variant;
12826 ChessMove r = GameUnfinished;
12829 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12830 r = gameInfo.result;
12831 p = gameInfo.resultDetails;
12832 gameInfo.resultDetails = NULL;
12834 ClearGameInfo(&gameInfo);
12835 gameInfo.variant = v;
12837 switch (gameMode) {
12838 case MachinePlaysWhite:
12839 gameInfo.event = StrSave( appData.pgnEventHeader );
12840 gameInfo.site = StrSave(HostName());
12841 gameInfo.date = PGNDate();
12842 gameInfo.round = StrSave("-");
12843 gameInfo.white = StrSave(first.tidy);
12844 gameInfo.black = StrSave(UserName());
12845 gameInfo.timeControl = TimeControlTagValue();
12848 case MachinePlaysBlack:
12849 gameInfo.event = StrSave( appData.pgnEventHeader );
12850 gameInfo.site = StrSave(HostName());
12851 gameInfo.date = PGNDate();
12852 gameInfo.round = StrSave("-");
12853 gameInfo.white = StrSave(UserName());
12854 gameInfo.black = StrSave(first.tidy);
12855 gameInfo.timeControl = TimeControlTagValue();
12858 case TwoMachinesPlay:
12859 gameInfo.event = StrSave( appData.pgnEventHeader );
12860 gameInfo.site = StrSave(HostName());
12861 gameInfo.date = PGNDate();
12862 if (matchGame > 0) {
12864 sprintf(buf, "%d", matchGame);
12865 gameInfo.round = StrSave(buf);
12867 gameInfo.round = StrSave("-");
12869 if (first.twoMachinesColor[0] == 'w') {
12870 gameInfo.white = StrSave(first.tidy);
12871 gameInfo.black = StrSave(second.tidy);
12873 gameInfo.white = StrSave(second.tidy);
12874 gameInfo.black = StrSave(first.tidy);
12876 gameInfo.timeControl = TimeControlTagValue();
12880 gameInfo.event = StrSave("Edited game");
12881 gameInfo.site = StrSave(HostName());
12882 gameInfo.date = PGNDate();
12883 gameInfo.round = StrSave("-");
12884 gameInfo.white = StrSave("-");
12885 gameInfo.black = StrSave("-");
12886 gameInfo.result = r;
12887 gameInfo.resultDetails = p;
12891 gameInfo.event = StrSave("Edited position");
12892 gameInfo.site = StrSave(HostName());
12893 gameInfo.date = PGNDate();
12894 gameInfo.round = StrSave("-");
12895 gameInfo.white = StrSave("-");
12896 gameInfo.black = StrSave("-");
12899 case IcsPlayingWhite:
12900 case IcsPlayingBlack:
12905 case PlayFromGameFile:
12906 gameInfo.event = StrSave("Game from non-PGN file");
12907 gameInfo.site = StrSave(HostName());
12908 gameInfo.date = PGNDate();
12909 gameInfo.round = StrSave("-");
12910 gameInfo.white = StrSave("?");
12911 gameInfo.black = StrSave("?");
12920 ReplaceComment(index, text)
12926 while (*text == '\n') text++;
12927 len = strlen(text);
12928 while (len > 0 && text[len - 1] == '\n') len--;
12930 if (commentList[index] != NULL)
12931 free(commentList[index]);
12934 commentList[index] = NULL;
12937 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12938 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12939 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12940 commentList[index] = (char *) malloc(len + 2);
12941 strncpy(commentList[index], text, len);
12942 commentList[index][len] = '\n';
12943 commentList[index][len + 1] = NULLCHAR;
12945 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12947 commentList[index] = (char *) malloc(len + 6);
12948 strcpy(commentList[index], "{\n");
12949 strncpy(commentList[index]+2, text, len);
12950 commentList[index][len+2] = NULLCHAR;
12951 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12952 strcat(commentList[index], "\n}\n");
12966 if (ch == '\r') continue;
12968 } while (ch != '\0');
12972 AppendComment(index, text, addBraces)
12975 Boolean addBraces; // [HGM] braces: tells if we should add {}
12980 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12981 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12984 while (*text == '\n') text++;
12985 len = strlen(text);
12986 while (len > 0 && text[len - 1] == '\n') len--;
12988 if (len == 0) return;
12990 if (commentList[index] != NULL) {
12991 old = commentList[index];
12992 oldlen = strlen(old);
12993 while(commentList[index][oldlen-1] == '\n')
12994 commentList[index][--oldlen] = NULLCHAR;
12995 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12996 strcpy(commentList[index], old);
12998 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12999 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13000 if(addBraces) addBraces = FALSE; else { text++; len--; }
13001 while (*text == '\n') { text++; len--; }
13002 commentList[index][--oldlen] = NULLCHAR;
13004 if(addBraces) strcat(commentList[index], "\n{\n");
13005 else strcat(commentList[index], "\n");
13006 strcat(commentList[index], text);
13007 if(addBraces) strcat(commentList[index], "\n}\n");
13008 else strcat(commentList[index], "\n");
13010 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13012 strcpy(commentList[index], "{\n");
13013 else commentList[index][0] = NULLCHAR;
13014 strcat(commentList[index], text);
13015 strcat(commentList[index], "\n");
13016 if(addBraces) strcat(commentList[index], "}\n");
13020 static char * FindStr( char * text, char * sub_text )
13022 char * result = strstr( text, sub_text );
13024 if( result != NULL ) {
13025 result += strlen( sub_text );
13031 /* [AS] Try to extract PV info from PGN comment */
13032 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13033 char *GetInfoFromComment( int index, char * text )
13037 if( text != NULL && index > 0 ) {
13040 int time = -1, sec = 0, deci;
13041 char * s_eval = FindStr( text, "[%eval " );
13042 char * s_emt = FindStr( text, "[%emt " );
13044 if( s_eval != NULL || s_emt != NULL ) {
13048 if( s_eval != NULL ) {
13049 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13053 if( delim != ']' ) {
13058 if( s_emt != NULL ) {
13063 /* We expect something like: [+|-]nnn.nn/dd */
13066 if(*text != '{') return text; // [HGM] braces: must be normal comment
13068 sep = strchr( text, '/' );
13069 if( sep == NULL || sep < (text+4) ) {
13073 time = -1; sec = -1; deci = -1;
13074 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13075 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13076 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13077 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13081 if( score_lo < 0 || score_lo >= 100 ) {
13085 if(sec >= 0) time = 600*time + 10*sec; else
13086 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13088 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13090 /* [HGM] PV time: now locate end of PV info */
13091 while( *++sep >= '0' && *sep <= '9'); // strip depth
13093 while( *++sep >= '0' && *sep <= '9'); // strip time
13095 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13097 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13098 while(*sep == ' ') sep++;
13109 pvInfoList[index-1].depth = depth;
13110 pvInfoList[index-1].score = score;
13111 pvInfoList[index-1].time = 10*time; // centi-sec
13112 if(*sep == '}') *sep = 0; else *--sep = '{';
13118 SendToProgram(message, cps)
13120 ChessProgramState *cps;
13122 int count, outCount, error;
13125 if (cps->pr == NULL) return;
13128 if (appData.debugMode) {
13131 fprintf(debugFP, "%ld >%-6s: %s",
13132 SubtractTimeMarks(&now, &programStartTime),
13133 cps->which, message);
13136 count = strlen(message);
13137 outCount = OutputToProcess(cps->pr, message, count, &error);
13138 if (outCount < count && !exiting
13139 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13140 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13141 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13142 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13143 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13144 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13146 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13148 gameInfo.resultDetails = StrSave(buf);
13150 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13155 ReceiveFromProgram(isr, closure, message, count, error)
13156 InputSourceRef isr;
13164 ChessProgramState *cps = (ChessProgramState *)closure;
13166 if (isr != cps->isr) return; /* Killed intentionally */
13170 _("Error: %s chess program (%s) exited unexpectedly"),
13171 cps->which, cps->program);
13172 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13173 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13174 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13175 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13177 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13179 gameInfo.resultDetails = StrSave(buf);
13181 RemoveInputSource(cps->isr);
13182 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13185 _("Error reading from %s chess program (%s)"),
13186 cps->which, cps->program);
13187 RemoveInputSource(cps->isr);
13189 /* [AS] Program is misbehaving badly... kill it */
13190 if( count == -2 ) {
13191 DestroyChildProcess( cps->pr, 9 );
13195 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13200 if ((end_str = strchr(message, '\r')) != NULL)
13201 *end_str = NULLCHAR;
13202 if ((end_str = strchr(message, '\n')) != NULL)
13203 *end_str = NULLCHAR;
13205 if (appData.debugMode) {
13206 TimeMark now; int print = 1;
13207 char *quote = ""; char c; int i;
13209 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13210 char start = message[0];
13211 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13212 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13213 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13214 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13215 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13216 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13217 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13218 sscanf(message, "pong %c", &c)!=1 && start != '#')
13219 { quote = "# "; print = (appData.engineComments == 2); }
13220 message[0] = start; // restore original message
13224 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13225 SubtractTimeMarks(&now, &programStartTime), cps->which,
13231 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13232 if (appData.icsEngineAnalyze) {
13233 if (strstr(message, "whisper") != NULL ||
13234 strstr(message, "kibitz") != NULL ||
13235 strstr(message, "tellics") != NULL) return;
13238 HandleMachineMove(message, cps);
13243 SendTimeControl(cps, mps, tc, inc, sd, st)
13244 ChessProgramState *cps;
13245 int mps, inc, sd, st;
13251 if( timeControl_2 > 0 ) {
13252 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13253 tc = timeControl_2;
13256 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13257 inc /= cps->timeOdds;
13258 st /= cps->timeOdds;
13260 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13263 /* Set exact time per move, normally using st command */
13264 if (cps->stKludge) {
13265 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13267 if (seconds == 0) {
13268 sprintf(buf, "level 1 %d\n", st/60);
13270 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13273 sprintf(buf, "st %d\n", st);
13276 /* Set conventional or incremental time control, using level command */
13277 if (seconds == 0) {
13278 /* Note old gnuchess bug -- minutes:seconds used to not work.
13279 Fixed in later versions, but still avoid :seconds
13280 when seconds is 0. */
13281 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13283 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13284 seconds, inc/1000);
13287 SendToProgram(buf, cps);
13289 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13290 /* Orthogonally, limit search to given depth */
13292 if (cps->sdKludge) {
13293 sprintf(buf, "depth\n%d\n", sd);
13295 sprintf(buf, "sd %d\n", sd);
13297 SendToProgram(buf, cps);
13300 if(cps->nps > 0) { /* [HGM] nps */
13301 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13303 sprintf(buf, "nps %d\n", cps->nps);
13304 SendToProgram(buf, cps);
13309 ChessProgramState *WhitePlayer()
13310 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13312 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13313 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13319 SendTimeRemaining(cps, machineWhite)
13320 ChessProgramState *cps;
13321 int /*boolean*/ machineWhite;
13323 char message[MSG_SIZ];
13326 /* Note: this routine must be called when the clocks are stopped
13327 or when they have *just* been set or switched; otherwise
13328 it will be off by the time since the current tick started.
13330 if (machineWhite) {
13331 time = whiteTimeRemaining / 10;
13332 otime = blackTimeRemaining / 10;
13334 time = blackTimeRemaining / 10;
13335 otime = whiteTimeRemaining / 10;
13337 /* [HGM] translate opponent's time by time-odds factor */
13338 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13339 if (appData.debugMode) {
13340 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13343 if (time <= 0) time = 1;
13344 if (otime <= 0) otime = 1;
13346 sprintf(message, "time %ld\n", time);
13347 SendToProgram(message, cps);
13349 sprintf(message, "otim %ld\n", otime);
13350 SendToProgram(message, cps);
13354 BoolFeature(p, name, loc, cps)
13358 ChessProgramState *cps;
13361 int len = strlen(name);
13363 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13365 sscanf(*p, "%d", &val);
13367 while (**p && **p != ' ') (*p)++;
13368 sprintf(buf, "accepted %s\n", name);
13369 SendToProgram(buf, cps);
13376 IntFeature(p, name, loc, cps)
13380 ChessProgramState *cps;
13383 int len = strlen(name);
13384 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13386 sscanf(*p, "%d", loc);
13387 while (**p && **p != ' ') (*p)++;
13388 sprintf(buf, "accepted %s\n", name);
13389 SendToProgram(buf, cps);
13396 StringFeature(p, name, loc, cps)
13400 ChessProgramState *cps;
13403 int len = strlen(name);
13404 if (strncmp((*p), name, len) == 0
13405 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13407 sscanf(*p, "%[^\"]", loc);
13408 while (**p && **p != '\"') (*p)++;
13409 if (**p == '\"') (*p)++;
13410 sprintf(buf, "accepted %s\n", name);
13411 SendToProgram(buf, cps);
13418 ParseOption(Option *opt, ChessProgramState *cps)
13419 // [HGM] options: process the string that defines an engine option, and determine
13420 // name, type, default value, and allowed value range
13422 char *p, *q, buf[MSG_SIZ];
13423 int n, min = (-1)<<31, max = 1<<31, def;
13425 if(p = strstr(opt->name, " -spin ")) {
13426 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13427 if(max < min) max = min; // enforce consistency
13428 if(def < min) def = min;
13429 if(def > max) def = max;
13434 } else if((p = strstr(opt->name, " -slider "))) {
13435 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13436 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13437 if(max < min) max = min; // enforce consistency
13438 if(def < min) def = min;
13439 if(def > max) def = max;
13443 opt->type = Spin; // Slider;
13444 } else if((p = strstr(opt->name, " -string "))) {
13445 opt->textValue = p+9;
13446 opt->type = TextBox;
13447 } else if((p = strstr(opt->name, " -file "))) {
13448 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13449 opt->textValue = p+7;
13450 opt->type = TextBox; // FileName;
13451 } else if((p = strstr(opt->name, " -path "))) {
13452 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13453 opt->textValue = p+7;
13454 opt->type = TextBox; // PathName;
13455 } else if(p = strstr(opt->name, " -check ")) {
13456 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13457 opt->value = (def != 0);
13458 opt->type = CheckBox;
13459 } else if(p = strstr(opt->name, " -combo ")) {
13460 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13461 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13462 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13463 opt->value = n = 0;
13464 while(q = StrStr(q, " /// ")) {
13465 n++; *q = 0; // count choices, and null-terminate each of them
13467 if(*q == '*') { // remember default, which is marked with * prefix
13471 cps->comboList[cps->comboCnt++] = q;
13473 cps->comboList[cps->comboCnt++] = NULL;
13475 opt->type = ComboBox;
13476 } else if(p = strstr(opt->name, " -button")) {
13477 opt->type = Button;
13478 } else if(p = strstr(opt->name, " -save")) {
13479 opt->type = SaveButton;
13480 } else return FALSE;
13481 *p = 0; // terminate option name
13482 // now look if the command-line options define a setting for this engine option.
13483 if(cps->optionSettings && cps->optionSettings[0])
13484 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13485 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13486 sprintf(buf, "option %s", p);
13487 if(p = strstr(buf, ",")) *p = 0;
13489 SendToProgram(buf, cps);
13495 FeatureDone(cps, val)
13496 ChessProgramState* cps;
13499 DelayedEventCallback cb = GetDelayedEvent();
13500 if ((cb == InitBackEnd3 && cps == &first) ||
13501 (cb == TwoMachinesEventIfReady && cps == &second)) {
13502 CancelDelayedEvent();
13503 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13505 cps->initDone = val;
13508 /* Parse feature command from engine */
13510 ParseFeatures(args, cps)
13512 ChessProgramState *cps;
13520 while (*p == ' ') p++;
13521 if (*p == NULLCHAR) return;
13523 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13524 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13525 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13526 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13527 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13528 if (BoolFeature(&p, "reuse", &val, cps)) {
13529 /* Engine can disable reuse, but can't enable it if user said no */
13530 if (!val) cps->reuse = FALSE;
13533 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13534 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13535 if (gameMode == TwoMachinesPlay) {
13536 DisplayTwoMachinesTitle();
13542 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13543 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13544 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13545 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13546 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13547 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13548 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13549 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13550 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13551 if (IntFeature(&p, "done", &val, cps)) {
13552 FeatureDone(cps, val);
13555 /* Added by Tord: */
13556 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13557 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13558 /* End of additions by Tord */
13560 /* [HGM] added features: */
13561 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13562 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13563 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13564 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13565 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13566 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13567 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13568 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13569 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13570 SendToProgram(buf, cps);
13573 if(cps->nrOptions >= MAX_OPTIONS) {
13575 sprintf(buf, "%s engine has too many options\n", cps->which);
13576 DisplayError(buf, 0);
13580 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13581 /* End of additions by HGM */
13583 /* unknown feature: complain and skip */
13585 while (*q && *q != '=') q++;
13586 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13587 SendToProgram(buf, cps);
13593 while (*p && *p != '\"') p++;
13594 if (*p == '\"') p++;
13596 while (*p && *p != ' ') p++;
13604 PeriodicUpdatesEvent(newState)
13607 if (newState == appData.periodicUpdates)
13610 appData.periodicUpdates=newState;
13612 /* Display type changes, so update it now */
13613 // DisplayAnalysis();
13615 /* Get the ball rolling again... */
13617 AnalysisPeriodicEvent(1);
13618 StartAnalysisClock();
13623 PonderNextMoveEvent(newState)
13626 if (newState == appData.ponderNextMove) return;
13627 if (gameMode == EditPosition) EditPositionDone(TRUE);
13629 SendToProgram("hard\n", &first);
13630 if (gameMode == TwoMachinesPlay) {
13631 SendToProgram("hard\n", &second);
13634 SendToProgram("easy\n", &first);
13635 thinkOutput[0] = NULLCHAR;
13636 if (gameMode == TwoMachinesPlay) {
13637 SendToProgram("easy\n", &second);
13640 appData.ponderNextMove = newState;
13644 NewSettingEvent(option, command, value)
13650 if (gameMode == EditPosition) EditPositionDone(TRUE);
13651 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13652 SendToProgram(buf, &first);
13653 if (gameMode == TwoMachinesPlay) {
13654 SendToProgram(buf, &second);
13659 ShowThinkingEvent()
13660 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13662 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13663 int newState = appData.showThinking
13664 // [HGM] thinking: other features now need thinking output as well
13665 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13667 if (oldState == newState) return;
13668 oldState = newState;
13669 if (gameMode == EditPosition) EditPositionDone(TRUE);
13671 SendToProgram("post\n", &first);
13672 if (gameMode == TwoMachinesPlay) {
13673 SendToProgram("post\n", &second);
13676 SendToProgram("nopost\n", &first);
13677 thinkOutput[0] = NULLCHAR;
13678 if (gameMode == TwoMachinesPlay) {
13679 SendToProgram("nopost\n", &second);
13682 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13686 AskQuestionEvent(title, question, replyPrefix, which)
13687 char *title; char *question; char *replyPrefix; char *which;
13689 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13690 if (pr == NoProc) return;
13691 AskQuestion(title, question, replyPrefix, pr);
13695 DisplayMove(moveNumber)
13698 char message[MSG_SIZ];
13700 char cpThinkOutput[MSG_SIZ];
13702 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13704 if (moveNumber == forwardMostMove - 1 ||
13705 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13707 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13709 if (strchr(cpThinkOutput, '\n')) {
13710 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13713 *cpThinkOutput = NULLCHAR;
13716 /* [AS] Hide thinking from human user */
13717 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13718 *cpThinkOutput = NULLCHAR;
13719 if( thinkOutput[0] != NULLCHAR ) {
13722 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13723 cpThinkOutput[i] = '.';
13725 cpThinkOutput[i] = NULLCHAR;
13726 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13730 if (moveNumber == forwardMostMove - 1 &&
13731 gameInfo.resultDetails != NULL) {
13732 if (gameInfo.resultDetails[0] == NULLCHAR) {
13733 sprintf(res, " %s", PGNResult(gameInfo.result));
13735 sprintf(res, " {%s} %s",
13736 gameInfo.resultDetails, PGNResult(gameInfo.result));
13742 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13743 DisplayMessage(res, cpThinkOutput);
13745 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13746 WhiteOnMove(moveNumber) ? " " : ".. ",
13747 parseList[moveNumber], res);
13748 DisplayMessage(message, cpThinkOutput);
13753 DisplayComment(moveNumber, text)
13757 char title[MSG_SIZ];
13758 char buf[8000]; // comment can be long!
13761 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13762 strcpy(title, "Comment");
13764 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13765 WhiteOnMove(moveNumber) ? " " : ".. ",
13766 parseList[moveNumber]);
13768 // [HGM] PV info: display PV info together with (or as) comment
13769 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13770 if(text == NULL) text = "";
13771 score = pvInfoList[moveNumber].score;
13772 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13773 depth, (pvInfoList[moveNumber].time+50)/100, text);
13776 if (text != NULL && (appData.autoDisplayComment || commentUp))
13777 CommentPopUp(title, text);
13780 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13781 * might be busy thinking or pondering. It can be omitted if your
13782 * gnuchess is configured to stop thinking immediately on any user
13783 * input. However, that gnuchess feature depends on the FIONREAD
13784 * ioctl, which does not work properly on some flavors of Unix.
13788 ChessProgramState *cps;
13791 if (!cps->useSigint) return;
13792 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13793 switch (gameMode) {
13794 case MachinePlaysWhite:
13795 case MachinePlaysBlack:
13796 case TwoMachinesPlay:
13797 case IcsPlayingWhite:
13798 case IcsPlayingBlack:
13801 /* Skip if we know it isn't thinking */
13802 if (!cps->maybeThinking) return;
13803 if (appData.debugMode)
13804 fprintf(debugFP, "Interrupting %s\n", cps->which);
13805 InterruptChildProcess(cps->pr);
13806 cps->maybeThinking = FALSE;
13811 #endif /*ATTENTION*/
13817 if (whiteTimeRemaining <= 0) {
13820 if (appData.icsActive) {
13821 if (appData.autoCallFlag &&
13822 gameMode == IcsPlayingBlack && !blackFlag) {
13823 SendToICS(ics_prefix);
13824 SendToICS("flag\n");
13828 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13830 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13831 if (appData.autoCallFlag) {
13832 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13839 if (blackTimeRemaining <= 0) {
13842 if (appData.icsActive) {
13843 if (appData.autoCallFlag &&
13844 gameMode == IcsPlayingWhite && !whiteFlag) {
13845 SendToICS(ics_prefix);
13846 SendToICS("flag\n");
13850 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13852 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13853 if (appData.autoCallFlag) {
13854 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13867 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13868 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13871 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13873 if ( !WhiteOnMove(forwardMostMove) )
13874 /* White made time control */
13875 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13876 /* [HGM] time odds: correct new time quota for time odds! */
13877 / WhitePlayer()->timeOdds;
13879 /* Black made time control */
13880 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13881 / WhitePlayer()->other->timeOdds;
13885 DisplayBothClocks()
13887 int wom = gameMode == EditPosition ?
13888 !blackPlaysFirst : WhiteOnMove(currentMove);
13889 DisplayWhiteClock(whiteTimeRemaining, wom);
13890 DisplayBlackClock(blackTimeRemaining, !wom);
13894 /* Timekeeping seems to be a portability nightmare. I think everyone
13895 has ftime(), but I'm really not sure, so I'm including some ifdefs
13896 to use other calls if you don't. Clocks will be less accurate if
13897 you have neither ftime nor gettimeofday.
13900 /* VS 2008 requires the #include outside of the function */
13901 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13902 #include <sys/timeb.h>
13905 /* Get the current time as a TimeMark */
13910 #if HAVE_GETTIMEOFDAY
13912 struct timeval timeVal;
13913 struct timezone timeZone;
13915 gettimeofday(&timeVal, &timeZone);
13916 tm->sec = (long) timeVal.tv_sec;
13917 tm->ms = (int) (timeVal.tv_usec / 1000L);
13919 #else /*!HAVE_GETTIMEOFDAY*/
13922 // include <sys/timeb.h> / moved to just above start of function
13923 struct timeb timeB;
13926 tm->sec = (long) timeB.time;
13927 tm->ms = (int) timeB.millitm;
13929 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13930 tm->sec = (long) time(NULL);
13936 /* Return the difference in milliseconds between two
13937 time marks. We assume the difference will fit in a long!
13940 SubtractTimeMarks(tm2, tm1)
13941 TimeMark *tm2, *tm1;
13943 return 1000L*(tm2->sec - tm1->sec) +
13944 (long) (tm2->ms - tm1->ms);
13949 * Code to manage the game clocks.
13951 * In tournament play, black starts the clock and then white makes a move.
13952 * We give the human user a slight advantage if he is playing white---the
13953 * clocks don't run until he makes his first move, so it takes zero time.
13954 * Also, we don't account for network lag, so we could get out of sync
13955 * with GNU Chess's clock -- but then, referees are always right.
13958 static TimeMark tickStartTM;
13959 static long intendedTickLength;
13962 NextTickLength(timeRemaining)
13963 long timeRemaining;
13965 long nominalTickLength, nextTickLength;
13967 if (timeRemaining > 0L && timeRemaining <= 10000L)
13968 nominalTickLength = 100L;
13970 nominalTickLength = 1000L;
13971 nextTickLength = timeRemaining % nominalTickLength;
13972 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13974 return nextTickLength;
13977 /* Adjust clock one minute up or down */
13979 AdjustClock(Boolean which, int dir)
13981 if(which) blackTimeRemaining += 60000*dir;
13982 else whiteTimeRemaining += 60000*dir;
13983 DisplayBothClocks();
13986 /* Stop clocks and reset to a fresh time control */
13990 (void) StopClockTimer();
13991 if (appData.icsActive) {
13992 whiteTimeRemaining = blackTimeRemaining = 0;
13993 } else if (searchTime) {
13994 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13995 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13996 } else { /* [HGM] correct new time quote for time odds */
13997 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13998 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14000 if (whiteFlag || blackFlag) {
14002 whiteFlag = blackFlag = FALSE;
14004 DisplayBothClocks();
14007 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14009 /* Decrement running clock by amount of time that has passed */
14013 long timeRemaining;
14014 long lastTickLength, fudge;
14017 if (!appData.clockMode) return;
14018 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14022 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14024 /* Fudge if we woke up a little too soon */
14025 fudge = intendedTickLength - lastTickLength;
14026 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14028 if (WhiteOnMove(forwardMostMove)) {
14029 if(whiteNPS >= 0) lastTickLength = 0;
14030 timeRemaining = whiteTimeRemaining -= lastTickLength;
14031 DisplayWhiteClock(whiteTimeRemaining - fudge,
14032 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14034 if(blackNPS >= 0) lastTickLength = 0;
14035 timeRemaining = blackTimeRemaining -= lastTickLength;
14036 DisplayBlackClock(blackTimeRemaining - fudge,
14037 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14040 if (CheckFlags()) return;
14043 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14044 StartClockTimer(intendedTickLength);
14046 /* if the time remaining has fallen below the alarm threshold, sound the
14047 * alarm. if the alarm has sounded and (due to a takeback or time control
14048 * with increment) the time remaining has increased to a level above the
14049 * threshold, reset the alarm so it can sound again.
14052 if (appData.icsActive && appData.icsAlarm) {
14054 /* make sure we are dealing with the user's clock */
14055 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14056 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14059 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14060 alarmSounded = FALSE;
14061 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14063 alarmSounded = TRUE;
14069 /* A player has just moved, so stop the previously running
14070 clock and (if in clock mode) start the other one.
14071 We redisplay both clocks in case we're in ICS mode, because
14072 ICS gives us an update to both clocks after every move.
14073 Note that this routine is called *after* forwardMostMove
14074 is updated, so the last fractional tick must be subtracted
14075 from the color that is *not* on move now.
14078 SwitchClocks(int newMoveNr)
14080 long lastTickLength;
14082 int flagged = FALSE;
14086 if (StopClockTimer() && appData.clockMode) {
14087 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14088 if (!WhiteOnMove(forwardMostMove)) {
14089 if(blackNPS >= 0) lastTickLength = 0;
14090 blackTimeRemaining -= lastTickLength;
14091 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14092 // if(pvInfoList[forwardMostMove-1].time == -1)
14093 pvInfoList[forwardMostMove-1].time = // use GUI time
14094 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14096 if(whiteNPS >= 0) lastTickLength = 0;
14097 whiteTimeRemaining -= lastTickLength;
14098 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14099 // if(pvInfoList[forwardMostMove-1].time == -1)
14100 pvInfoList[forwardMostMove-1].time =
14101 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14103 flagged = CheckFlags();
14105 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14106 CheckTimeControl();
14108 if (flagged || !appData.clockMode) return;
14110 switch (gameMode) {
14111 case MachinePlaysBlack:
14112 case MachinePlaysWhite:
14113 case BeginningOfGame:
14114 if (pausing) return;
14118 case PlayFromGameFile:
14126 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14127 if(WhiteOnMove(forwardMostMove))
14128 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14129 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14133 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14134 whiteTimeRemaining : blackTimeRemaining);
14135 StartClockTimer(intendedTickLength);
14139 /* Stop both clocks */
14143 long lastTickLength;
14146 if (!StopClockTimer()) return;
14147 if (!appData.clockMode) return;
14151 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14152 if (WhiteOnMove(forwardMostMove)) {
14153 if(whiteNPS >= 0) lastTickLength = 0;
14154 whiteTimeRemaining -= lastTickLength;
14155 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14157 if(blackNPS >= 0) lastTickLength = 0;
14158 blackTimeRemaining -= lastTickLength;
14159 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14164 /* Start clock of player on move. Time may have been reset, so
14165 if clock is already running, stop and restart it. */
14169 (void) StopClockTimer(); /* in case it was running already */
14170 DisplayBothClocks();
14171 if (CheckFlags()) return;
14173 if (!appData.clockMode) return;
14174 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14176 GetTimeMark(&tickStartTM);
14177 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14178 whiteTimeRemaining : blackTimeRemaining);
14180 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14181 whiteNPS = blackNPS = -1;
14182 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14183 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14184 whiteNPS = first.nps;
14185 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14186 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14187 blackNPS = first.nps;
14188 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14189 whiteNPS = second.nps;
14190 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14191 blackNPS = second.nps;
14192 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14194 StartClockTimer(intendedTickLength);
14201 long second, minute, hour, day;
14203 static char buf[32];
14205 if (ms > 0 && ms <= 9900) {
14206 /* convert milliseconds to tenths, rounding up */
14207 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14209 sprintf(buf, " %03.1f ", tenths/10.0);
14213 /* convert milliseconds to seconds, rounding up */
14214 /* use floating point to avoid strangeness of integer division
14215 with negative dividends on many machines */
14216 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14223 day = second / (60 * 60 * 24);
14224 second = second % (60 * 60 * 24);
14225 hour = second / (60 * 60);
14226 second = second % (60 * 60);
14227 minute = second / 60;
14228 second = second % 60;
14231 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14232 sign, day, hour, minute, second);
14234 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14236 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14243 * This is necessary because some C libraries aren't ANSI C compliant yet.
14246 StrStr(string, match)
14247 char *string, *match;
14251 length = strlen(match);
14253 for (i = strlen(string) - length; i >= 0; i--, string++)
14254 if (!strncmp(match, string, length))
14261 StrCaseStr(string, match)
14262 char *string, *match;
14266 length = strlen(match);
14268 for (i = strlen(string) - length; i >= 0; i--, string++) {
14269 for (j = 0; j < length; j++) {
14270 if (ToLower(match[j]) != ToLower(string[j]))
14273 if (j == length) return string;
14287 c1 = ToLower(*s1++);
14288 c2 = ToLower(*s2++);
14289 if (c1 > c2) return 1;
14290 if (c1 < c2) return -1;
14291 if (c1 == NULLCHAR) return 0;
14300 return isupper(c) ? tolower(c) : c;
14308 return islower(c) ? toupper(c) : c;
14310 #endif /* !_amigados */
14318 if ((ret = (char *) malloc(strlen(s) + 1))) {
14325 StrSavePtr(s, savePtr)
14326 char *s, **savePtr;
14331 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14332 strcpy(*savePtr, s);
14344 clock = time((time_t *)NULL);
14345 tm = localtime(&clock);
14346 sprintf(buf, "%04d.%02d.%02d",
14347 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14348 return StrSave(buf);
14353 PositionToFEN(move, overrideCastling)
14355 char *overrideCastling;
14357 int i, j, fromX, fromY, toX, toY;
14364 whiteToPlay = (gameMode == EditPosition) ?
14365 !blackPlaysFirst : (move % 2 == 0);
14368 /* Piece placement data */
14369 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14371 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14372 if (boards[move][i][j] == EmptySquare) {
14374 } else { ChessSquare piece = boards[move][i][j];
14375 if (emptycount > 0) {
14376 if(emptycount<10) /* [HGM] can be >= 10 */
14377 *p++ = '0' + emptycount;
14378 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14381 if(PieceToChar(piece) == '+') {
14382 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14384 piece = (ChessSquare)(DEMOTED piece);
14386 *p++ = PieceToChar(piece);
14388 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14389 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14394 if (emptycount > 0) {
14395 if(emptycount<10) /* [HGM] can be >= 10 */
14396 *p++ = '0' + emptycount;
14397 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14404 /* [HGM] print Crazyhouse or Shogi holdings */
14405 if( gameInfo.holdingsWidth ) {
14406 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14408 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14409 piece = boards[move][i][BOARD_WIDTH-1];
14410 if( piece != EmptySquare )
14411 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14412 *p++ = PieceToChar(piece);
14414 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14415 piece = boards[move][BOARD_HEIGHT-i-1][0];
14416 if( piece != EmptySquare )
14417 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14418 *p++ = PieceToChar(piece);
14421 if( q == p ) *p++ = '-';
14427 *p++ = whiteToPlay ? 'w' : 'b';
14430 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14431 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14433 if(nrCastlingRights) {
14435 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14436 /* [HGM] write directly from rights */
14437 if(boards[move][CASTLING][2] != NoRights &&
14438 boards[move][CASTLING][0] != NoRights )
14439 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14440 if(boards[move][CASTLING][2] != NoRights &&
14441 boards[move][CASTLING][1] != NoRights )
14442 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14443 if(boards[move][CASTLING][5] != NoRights &&
14444 boards[move][CASTLING][3] != NoRights )
14445 *p++ = boards[move][CASTLING][3] + AAA;
14446 if(boards[move][CASTLING][5] != NoRights &&
14447 boards[move][CASTLING][4] != NoRights )
14448 *p++ = boards[move][CASTLING][4] + AAA;
14451 /* [HGM] write true castling rights */
14452 if( nrCastlingRights == 6 ) {
14453 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14454 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14455 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14456 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14457 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14458 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14459 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14460 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14463 if (q == p) *p++ = '-'; /* No castling rights */
14467 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14468 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14469 /* En passant target square */
14470 if (move > backwardMostMove) {
14471 fromX = moveList[move - 1][0] - AAA;
14472 fromY = moveList[move - 1][1] - ONE;
14473 toX = moveList[move - 1][2] - AAA;
14474 toY = moveList[move - 1][3] - ONE;
14475 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14476 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14477 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14479 /* 2-square pawn move just happened */
14481 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14485 } else if(move == backwardMostMove) {
14486 // [HGM] perhaps we should always do it like this, and forget the above?
14487 if((signed char)boards[move][EP_STATUS] >= 0) {
14488 *p++ = boards[move][EP_STATUS] + AAA;
14489 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14500 /* [HGM] find reversible plies */
14501 { int i = 0, j=move;
14503 if (appData.debugMode) { int k;
14504 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14505 for(k=backwardMostMove; k<=forwardMostMove; k++)
14506 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14510 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14511 if( j == backwardMostMove ) i += initialRulePlies;
14512 sprintf(p, "%d ", i);
14513 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14515 /* Fullmove number */
14516 sprintf(p, "%d", (move / 2) + 1);
14518 return StrSave(buf);
14522 ParseFEN(board, blackPlaysFirst, fen)
14524 int *blackPlaysFirst;
14534 /* [HGM] by default clear Crazyhouse holdings, if present */
14535 if(gameInfo.holdingsWidth) {
14536 for(i=0; i<BOARD_HEIGHT; i++) {
14537 board[i][0] = EmptySquare; /* black holdings */
14538 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14539 board[i][1] = (ChessSquare) 0; /* black counts */
14540 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14544 /* Piece placement data */
14545 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14548 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14549 if (*p == '/') p++;
14550 emptycount = gameInfo.boardWidth - j;
14551 while (emptycount--)
14552 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14554 #if(BOARD_FILES >= 10)
14555 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14556 p++; emptycount=10;
14557 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14558 while (emptycount--)
14559 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14561 } else if (isdigit(*p)) {
14562 emptycount = *p++ - '0';
14563 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14564 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14565 while (emptycount--)
14566 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14567 } else if (*p == '+' || isalpha(*p)) {
14568 if (j >= gameInfo.boardWidth) return FALSE;
14570 piece = CharToPiece(*++p);
14571 if(piece == EmptySquare) return FALSE; /* unknown piece */
14572 piece = (ChessSquare) (PROMOTED piece ); p++;
14573 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14574 } else piece = CharToPiece(*p++);
14576 if(piece==EmptySquare) return FALSE; /* unknown piece */
14577 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14578 piece = (ChessSquare) (PROMOTED piece);
14579 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14582 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14588 while (*p == '/' || *p == ' ') p++;
14590 /* [HGM] look for Crazyhouse holdings here */
14591 while(*p==' ') p++;
14592 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14594 if(*p == '-' ) *p++; /* empty holdings */ else {
14595 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14596 /* if we would allow FEN reading to set board size, we would */
14597 /* have to add holdings and shift the board read so far here */
14598 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14600 if((int) piece >= (int) BlackPawn ) {
14601 i = (int)piece - (int)BlackPawn;
14602 i = PieceToNumber((ChessSquare)i);
14603 if( i >= gameInfo.holdingsSize ) return FALSE;
14604 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14605 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14607 i = (int)piece - (int)WhitePawn;
14608 i = PieceToNumber((ChessSquare)i);
14609 if( i >= gameInfo.holdingsSize ) return FALSE;
14610 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14611 board[i][BOARD_WIDTH-2]++; /* black holdings */
14615 if(*p == ']') *p++;
14618 while(*p == ' ') p++;
14623 *blackPlaysFirst = FALSE;
14626 *blackPlaysFirst = TRUE;
14632 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14633 /* return the extra info in global variiables */
14635 /* set defaults in case FEN is incomplete */
14636 board[EP_STATUS] = EP_UNKNOWN;
14637 for(i=0; i<nrCastlingRights; i++ ) {
14638 board[CASTLING][i] =
14639 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14640 } /* assume possible unless obviously impossible */
14641 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14642 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14643 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14644 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14645 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14646 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14647 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14648 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14651 while(*p==' ') p++;
14652 if(nrCastlingRights) {
14653 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14654 /* castling indicator present, so default becomes no castlings */
14655 for(i=0; i<nrCastlingRights; i++ ) {
14656 board[CASTLING][i] = NoRights;
14659 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14660 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14661 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14662 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14663 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14665 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14666 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14667 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14669 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14670 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14671 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14672 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14673 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14674 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14677 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14678 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14679 board[CASTLING][2] = whiteKingFile;
14682 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14683 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14684 board[CASTLING][2] = whiteKingFile;
14687 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14688 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14689 board[CASTLING][5] = blackKingFile;
14692 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14693 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14694 board[CASTLING][5] = blackKingFile;
14697 default: /* FRC castlings */
14698 if(c >= 'a') { /* black rights */
14699 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14700 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14701 if(i == BOARD_RGHT) break;
14702 board[CASTLING][5] = i;
14704 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14705 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14707 board[CASTLING][3] = c;
14709 board[CASTLING][4] = c;
14710 } else { /* white rights */
14711 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14712 if(board[0][i] == WhiteKing) break;
14713 if(i == BOARD_RGHT) break;
14714 board[CASTLING][2] = i;
14715 c -= AAA - 'a' + 'A';
14716 if(board[0][c] >= WhiteKing) break;
14718 board[CASTLING][0] = c;
14720 board[CASTLING][1] = c;
14724 for(i=0; i<nrCastlingRights; i++)
14725 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14726 if (appData.debugMode) {
14727 fprintf(debugFP, "FEN castling rights:");
14728 for(i=0; i<nrCastlingRights; i++)
14729 fprintf(debugFP, " %d", board[CASTLING][i]);
14730 fprintf(debugFP, "\n");
14733 while(*p==' ') p++;
14736 /* read e.p. field in games that know e.p. capture */
14737 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14738 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14740 p++; board[EP_STATUS] = EP_NONE;
14742 char c = *p++ - AAA;
14744 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14745 if(*p >= '0' && *p <='9') *p++;
14746 board[EP_STATUS] = c;
14751 if(sscanf(p, "%d", &i) == 1) {
14752 FENrulePlies = i; /* 50-move ply counter */
14753 /* (The move number is still ignored) */
14760 EditPositionPasteFEN(char *fen)
14763 Board initial_position;
14765 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14766 DisplayError(_("Bad FEN position in clipboard"), 0);
14769 int savedBlackPlaysFirst = blackPlaysFirst;
14770 EditPositionEvent();
14771 blackPlaysFirst = savedBlackPlaysFirst;
14772 CopyBoard(boards[0], initial_position);
14773 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14774 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14775 DisplayBothClocks();
14776 DrawPosition(FALSE, boards[currentMove]);
14781 static char cseq[12] = "\\ ";
14783 Boolean set_cont_sequence(char *new_seq)
14788 // handle bad attempts to set the sequence
14790 return 0; // acceptable error - no debug
14792 len = strlen(new_seq);
14793 ret = (len > 0) && (len < sizeof(cseq));
14795 strcpy(cseq, new_seq);
14796 else if (appData.debugMode)
14797 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14802 reformat a source message so words don't cross the width boundary. internal
14803 newlines are not removed. returns the wrapped size (no null character unless
14804 included in source message). If dest is NULL, only calculate the size required
14805 for the dest buffer. lp argument indicats line position upon entry, and it's
14806 passed back upon exit.
14808 int wrap(char *dest, char *src, int count, int width, int *lp)
14810 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14812 cseq_len = strlen(cseq);
14813 old_line = line = *lp;
14814 ansi = len = clen = 0;
14816 for (i=0; i < count; i++)
14818 if (src[i] == '\033')
14821 // if we hit the width, back up
14822 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14824 // store i & len in case the word is too long
14825 old_i = i, old_len = len;
14827 // find the end of the last word
14828 while (i && src[i] != ' ' && src[i] != '\n')
14834 // word too long? restore i & len before splitting it
14835 if ((old_i-i+clen) >= width)
14842 if (i && src[i-1] == ' ')
14845 if (src[i] != ' ' && src[i] != '\n')
14852 // now append the newline and continuation sequence
14857 strncpy(dest+len, cseq, cseq_len);
14865 dest[len] = src[i];
14869 if (src[i] == '\n')
14874 if (dest && appData.debugMode)
14876 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14877 count, width, line, len, *lp);
14878 show_bytes(debugFP, src, count);
14879 fprintf(debugFP, "\ndest: ");
14880 show_bytes(debugFP, dest, len);
14881 fprintf(debugFP, "\n");
14883 *lp = dest ? line : old_line;
14888 // [HGM] vari: routines for shelving variations
14891 PushTail(int firstMove, int lastMove)
14893 int i, j, nrMoves = lastMove - firstMove;
14895 if(appData.icsActive) { // only in local mode
14896 forwardMostMove = currentMove; // mimic old ICS behavior
14899 if(storedGames >= MAX_VARIATIONS-1) return;
14901 // push current tail of game on stack
14902 savedResult[storedGames] = gameInfo.result;
14903 savedDetails[storedGames] = gameInfo.resultDetails;
14904 gameInfo.resultDetails = NULL;
14905 savedFirst[storedGames] = firstMove;
14906 savedLast [storedGames] = lastMove;
14907 savedFramePtr[storedGames] = framePtr;
14908 framePtr -= nrMoves; // reserve space for the boards
14909 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14910 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14911 for(j=0; j<MOVE_LEN; j++)
14912 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14913 for(j=0; j<2*MOVE_LEN; j++)
14914 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14915 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14916 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14917 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14918 pvInfoList[firstMove+i-1].depth = 0;
14919 commentList[framePtr+i] = commentList[firstMove+i];
14920 commentList[firstMove+i] = NULL;
14924 forwardMostMove = currentMove; // truncte game so we can start variation
14925 if(storedGames == 1) GreyRevert(FALSE);
14929 PopTail(Boolean annotate)
14932 char buf[8000], moveBuf[20];
14934 if(appData.icsActive) return FALSE; // only in local mode
14935 if(!storedGames) return FALSE; // sanity
14938 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14939 nrMoves = savedLast[storedGames] - currentMove;
14942 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14943 else strcpy(buf, "(");
14944 for(i=currentMove; i<forwardMostMove; i++) {
14946 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14947 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14948 strcat(buf, moveBuf);
14949 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14953 for(i=1; i<nrMoves; i++) { // copy last variation back
14954 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14955 for(j=0; j<MOVE_LEN; j++)
14956 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14957 for(j=0; j<2*MOVE_LEN; j++)
14958 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14959 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14960 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14961 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14962 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14963 commentList[currentMove+i] = commentList[framePtr+i];
14964 commentList[framePtr+i] = NULL;
14966 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14967 framePtr = savedFramePtr[storedGames];
14968 gameInfo.result = savedResult[storedGames];
14969 if(gameInfo.resultDetails != NULL) {
14970 free(gameInfo.resultDetails);
14972 gameInfo.resultDetails = savedDetails[storedGames];
14973 forwardMostMove = currentMove + nrMoves;
14974 if(storedGames == 0) GreyRevert(TRUE);
14980 { // remove all shelved variations
14982 for(i=0; i<storedGames; i++) {
14983 if(savedDetails[i])
14984 free(savedDetails[i]);
14985 savedDetails[i] = NULL;
14987 for(i=framePtr; i<MAX_MOVES; i++) {
14988 if(commentList[i]) free(commentList[i]);
14989 commentList[i] = NULL;
14991 framePtr = MAX_MOVES-1;