2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
237 extern void ConsoleCreate();
240 ChessProgramState *WhitePlayer();
241 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
242 int VerifyDisplayMode P(());
244 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
245 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
246 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
247 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
248 void ics_update_width P((int new_width));
249 extern char installDir[MSG_SIZ];
250 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 extern int tinyLayout, smallLayout;
253 ChessProgramStats programStats;
254 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
256 static int exiting = 0; /* [HGM] moved to top */
257 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
258 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
259 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
260 int partnerHighlight[2];
261 Boolean partnerBoardValid = 0;
262 char partnerStatus[MSG_SIZ];
264 Boolean originalFlip;
265 Boolean twoBoards = 0;
266 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
267 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
268 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
269 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
270 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
271 int opponentKibitzes;
272 int lastSavedGame; /* [HGM] save: ID of game */
273 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
274 extern int chatCount;
276 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
277 char lastMsg[MSG_SIZ];
278 ChessSquare pieceSweep = EmptySquare;
279 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
280 int promoDefaultAltered;
282 /* States for ics_getting_history */
284 #define H_REQUESTED 1
285 #define H_GOT_REQ_HEADER 2
286 #define H_GOT_UNREQ_HEADER 3
287 #define H_GETTING_MOVES 4
288 #define H_GOT_UNWANTED_HEADER 5
290 /* whosays values for GameEnds */
299 /* Maximum number of games in a cmail message */
300 #define CMAIL_MAX_GAMES 20
302 /* Different types of move when calling RegisterMove */
304 #define CMAIL_RESIGN 1
306 #define CMAIL_ACCEPT 3
308 /* Different types of result to remember for each game */
309 #define CMAIL_NOT_RESULT 0
310 #define CMAIL_OLD_RESULT 1
311 #define CMAIL_NEW_RESULT 2
313 /* Telnet protocol constants */
324 safeStrCpy( char *dst, const char *src, size_t count )
327 assert( dst != NULL );
328 assert( src != NULL );
331 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
332 if( i == count && dst[count-1] != NULLCHAR)
334 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
335 if(appData.debugMode)
336 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
342 /* Some compiler can't cast u64 to double
343 * This function do the job for us:
345 * We use the highest bit for cast, this only
346 * works if the highest bit is not
347 * in use (This should not happen)
349 * We used this for all compiler
352 u64ToDouble(u64 value)
355 u64 tmp = value & u64Const(0x7fffffffffffffff);
356 r = (double)(s64)tmp;
357 if (value & u64Const(0x8000000000000000))
358 r += 9.2233720368547758080e18; /* 2^63 */
362 /* Fake up flags for now, as we aren't keeping track of castling
363 availability yet. [HGM] Change of logic: the flag now only
364 indicates the type of castlings allowed by the rule of the game.
365 The actual rights themselves are maintained in the array
366 castlingRights, as part of the game history, and are not probed
372 int flags = F_ALL_CASTLE_OK;
373 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
374 switch (gameInfo.variant) {
376 flags &= ~F_ALL_CASTLE_OK;
377 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
378 flags |= F_IGNORE_CHECK;
380 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
385 case VariantKriegspiel:
386 flags |= F_KRIEGSPIEL_CAPTURE;
388 case VariantCapaRandom:
389 case VariantFischeRandom:
390 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
391 case VariantNoCastle:
392 case VariantShatranj:
395 flags &= ~F_ALL_CASTLE_OK;
403 FILE *gameFileFP, *debugFP;
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
474 /* animateTraining preserves the state of appData.animate
475 * when Training mode is activated. This allows the
476 * response to be animated when appData.animate == TRUE and
477 * appData.animateDragging == TRUE.
479 Boolean animateTraining;
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char initialRights[BOARD_FILES];
489 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int initialRulePlies, FENrulePlies;
491 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int mute; // mute all sounds
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void CleanupTail P((void));
510 ChessSquare FIDEArray[2][BOARD_FILES] = {
511 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514 BlackKing, BlackBishop, BlackKnight, BlackRook }
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackKing, BlackKnight, BlackRook }
524 ChessSquare KnightmateArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527 { BlackRook, BlackMan, BlackBishop, BlackQueen,
528 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackMan, BlackFerz,
556 BlackKing, BlackMan, BlackKnight, BlackRook }
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
597 ChessSquare GothicArray[2][BOARD_FILES] = {
598 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
599 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
600 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
601 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 #define GothicArray CapablancaArray
608 ChessSquare FalconArray[2][BOARD_FILES] = {
609 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
610 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
611 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
612 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
615 #define FalconArray CapablancaArray
618 #else // !(BOARD_FILES>=10)
619 #define XiangqiPosition FIDEArray
620 #define CapablancaArray FIDEArray
621 #define GothicArray FIDEArray
622 #define GreatArray FIDEArray
623 #endif // !(BOARD_FILES>=10)
625 #if (BOARD_FILES>=12)
626 ChessSquare CourierArray[2][BOARD_FILES] = {
627 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
628 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
629 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
630 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
632 #else // !(BOARD_FILES>=12)
633 #define CourierArray CapablancaArray
634 #endif // !(BOARD_FILES>=12)
637 Board initialPosition;
640 /* Convert str to a rating. Checks for special cases of "----",
642 "++++", etc. Also strips ()'s */
644 string_to_rating(str)
647 while(*str && !isdigit(*str)) ++str;
649 return 0; /* One of the special "no rating" cases */
657 /* Init programStats */
658 programStats.movelist[0] = 0;
659 programStats.depth = 0;
660 programStats.nr_moves = 0;
661 programStats.moves_left = 0;
662 programStats.nodes = 0;
663 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
664 programStats.score = 0;
665 programStats.got_only_move = 0;
666 programStats.got_fail = 0;
667 programStats.line_is_book = 0;
672 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
673 if (appData.firstPlaysBlack) {
674 first.twoMachinesColor = "black\n";
675 second.twoMachinesColor = "white\n";
677 first.twoMachinesColor = "white\n";
678 second.twoMachinesColor = "black\n";
681 first.other = &second;
682 second.other = &first;
685 if(appData.timeOddsMode) {
686 norm = appData.timeOdds[0];
687 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689 first.timeOdds = appData.timeOdds[0]/norm;
690 second.timeOdds = appData.timeOdds[1]/norm;
693 if(programVersion) free(programVersion);
694 if (appData.noChessProgram) {
695 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
696 sprintf(programVersion, "%s", PACKAGE_STRING);
698 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
699 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
700 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705 UnloadEngine(ChessProgramState *cps)
707 /* Kill off first chess program */
708 if (cps->isr != NULL)
709 RemoveInputSource(cps->isr);
712 if (cps->pr != NoProc) {
714 DoSleep( appData.delayBeforeQuit );
715 SendToProgram("quit\n", cps);
716 DoSleep( appData.delayAfterQuit );
717 DestroyChildProcess(cps->pr, cps->useSigterm);
720 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 ClearOptions(ChessProgramState *cps)
727 cps->nrOptions = cps->comboCnt = 0;
728 for(i=0; i<MAX_OPTIONS; i++) {
729 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
730 cps->option[i].textValue = 0;
734 char *engineNames[] = {
740 InitEngine(ChessProgramState *cps, int n)
741 { // [HGM] all engine initialiation put in a function that does one engine
745 cps->which = engineNames[n];
746 cps->maybeThinking = FALSE;
750 cps->sendDrawOffers = 1;
752 cps->program = appData.chessProgram[n];
753 cps->host = appData.host[n];
754 cps->dir = appData.directory[n];
755 cps->initString = appData.engInitString[n];
756 cps->computerString = appData.computerString[n];
757 cps->useSigint = TRUE;
758 cps->useSigterm = TRUE;
759 cps->reuse = appData.reuse[n];
760 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
761 cps->useSetboard = FALSE;
763 cps->usePing = FALSE;
766 cps->usePlayother = FALSE;
767 cps->useColors = TRUE;
768 cps->useUsermove = FALSE;
769 cps->sendICS = FALSE;
770 cps->sendName = appData.icsActive;
771 cps->sdKludge = FALSE;
772 cps->stKludge = FALSE;
773 TidyProgramName(cps->program, cps->host, cps->tidy);
775 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
776 cps->analysisSupport = 2; /* detect */
777 cps->analyzing = FALSE;
778 cps->initDone = FALSE;
780 /* New features added by Tord: */
781 cps->useFEN960 = FALSE;
782 cps->useOOCastle = TRUE;
783 /* End of new features added by Tord. */
784 cps->fenOverride = appData.fenOverride[n];
786 /* [HGM] time odds: set factor for each machine */
787 cps->timeOdds = appData.timeOdds[n];
789 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
790 cps->accumulateTC = appData.accumulateTC[n];
791 cps->maxNrOfSessions = 1;
795 cps->supportsNPS = UNKNOWN;
798 cps->optionSettings = appData.engOptions[n];
800 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
801 cps->isUCI = appData.isUCI[n]; /* [AS] */
802 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
804 if (appData.protocolVersion[n] > PROTOVER
805 || appData.protocolVersion[n] < 1)
810 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
811 appData.protocolVersion[n]);
812 if( (len > MSG_SIZ) && appData.debugMode )
813 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
815 DisplayFatalError(buf, 0, 2);
819 cps->protocolVersion = appData.protocolVersion[n];
822 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
825 ChessProgramState *savCps;
831 if(WaitForEngine(savCps, LoadEngine)) return;
832 CommonEngineInit(); // recalculate time odds
833 if(gameInfo.variant != StringToVariant(appData.variant)) {
834 // we changed variant when loading the engine; this forces us to reset
835 Reset(TRUE, savCps != &first);
836 EditGameEvent(); // for consistency with other path, as Reset changes mode
838 InitChessProgram(savCps, FALSE);
839 SendToProgram("force\n", savCps);
840 DisplayMessage("", "");
841 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
842 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
848 ReplaceEngine(ChessProgramState *cps, int n)
852 appData.noChessProgram = FALSE;
853 appData.clockMode = TRUE;
855 if(n) return; // only startup first engine immediately; second can wait
856 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
860 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
861 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
863 void Load(ChessProgramState *cps, int i)
865 char *p, *q, buf[MSG_SIZ];
866 if(engineLine[0]) { // an engine was selected from the combo box
867 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
868 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
869 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
870 ParseArgsFromString(buf);
872 ReplaceEngine(cps, i);
876 while(q = strchr(p, SLASH)) p = q+1;
877 if(*p== NULLCHAR) return;
878 appData.chessProgram[i] = strdup(p);
879 if(engineDir[0] != NULLCHAR)
880 appData.directory[i] = engineDir;
881 else if(p != engineName) { // derive directory from engine path, when not given
883 appData.directory[i] = strdup(engineName);
885 } else appData.directory[i] = ".";
886 appData.isUCI[i] = isUCI;
887 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
888 appData.hasOwnBookUCI[i] = hasBook;
891 q = firstChessProgramNames;
892 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
893 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
894 v1 ? " -firstProtocolVersion 1" : "",
895 hasBook ? "" : " -fNoOwnBookUCI",
896 isUCI ? " -fUCI" : "",
897 storeVariant ? " -variant " : "",
898 storeVariant ? VariantName(gameInfo.variant) : "");
899 fprintf(debugFP, "new line: %s", buf);
900 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
901 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
904 ReplaceEngine(cps, i);
910 int matched, min, sec;
912 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
913 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
915 GetTimeMark(&programStartTime);
916 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
917 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
920 programStats.ok_to_send = 1;
921 programStats.seen_stat = 0;
924 * Initialize game list
930 * Internet chess server status
932 if (appData.icsActive) {
933 appData.matchMode = FALSE;
934 appData.matchGames = 0;
936 appData.noChessProgram = !appData.zippyPlay;
938 appData.zippyPlay = FALSE;
939 appData.zippyTalk = FALSE;
940 appData.noChessProgram = TRUE;
942 if (*appData.icsHelper != NULLCHAR) {
943 appData.useTelnet = TRUE;
944 appData.telnetProgram = appData.icsHelper;
947 appData.zippyTalk = appData.zippyPlay = FALSE;
950 /* [AS] Initialize pv info list [HGM] and game state */
954 for( i=0; i<=framePtr; i++ ) {
955 pvInfoList[i].depth = -1;
956 boards[i][EP_STATUS] = EP_NONE;
957 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
962 * Parse timeControl resource
964 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
965 appData.movesPerSession)) {
967 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
968 DisplayFatalError(buf, 0, 2);
972 * Parse searchTime resource
974 if (*appData.searchTime != NULLCHAR) {
975 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
977 searchTime = min * 60;
978 } else if (matched == 2) {
979 searchTime = min * 60 + sec;
982 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
983 DisplayFatalError(buf, 0, 2);
987 /* [AS] Adjudication threshold */
988 adjudicateLossThreshold = appData.adjudicateLossThreshold;
990 InitEngine(&first, 0);
991 InitEngine(&second, 1);
994 if (appData.icsActive) {
995 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
996 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
997 appData.clockMode = FALSE;
998 first.sendTime = second.sendTime = 0;
1002 /* Override some settings from environment variables, for backward
1003 compatibility. Unfortunately it's not feasible to have the env
1004 vars just set defaults, at least in xboard. Ugh.
1006 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1011 if (!appData.icsActive) {
1015 /* Check for variants that are supported only in ICS mode,
1016 or not at all. Some that are accepted here nevertheless
1017 have bugs; see comments below.
1019 VariantClass variant = StringToVariant(appData.variant);
1021 case VariantBughouse: /* need four players and two boards */
1022 case VariantKriegspiel: /* need to hide pieces and move details */
1023 /* case VariantFischeRandom: (Fabien: moved below) */
1024 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1025 if( (len > MSG_SIZ) && appData.debugMode )
1026 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1028 DisplayFatalError(buf, 0, 2);
1031 case VariantUnknown:
1032 case VariantLoadable:
1042 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1043 if( (len > MSG_SIZ) && appData.debugMode )
1044 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1046 DisplayFatalError(buf, 0, 2);
1049 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1050 case VariantFairy: /* [HGM] TestLegality definitely off! */
1051 case VariantGothic: /* [HGM] should work */
1052 case VariantCapablanca: /* [HGM] should work */
1053 case VariantCourier: /* [HGM] initial forced moves not implemented */
1054 case VariantShogi: /* [HGM] could still mate with pawn drop */
1055 case VariantKnightmate: /* [HGM] should work */
1056 case VariantCylinder: /* [HGM] untested */
1057 case VariantFalcon: /* [HGM] untested */
1058 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1059 offboard interposition not understood */
1060 case VariantNormal: /* definitely works! */
1061 case VariantWildCastle: /* pieces not automatically shuffled */
1062 case VariantNoCastle: /* pieces not automatically shuffled */
1063 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1064 case VariantLosers: /* should work except for win condition,
1065 and doesn't know captures are mandatory */
1066 case VariantSuicide: /* should work except for win condition,
1067 and doesn't know captures are mandatory */
1068 case VariantGiveaway: /* should work except for win condition,
1069 and doesn't know captures are mandatory */
1070 case VariantTwoKings: /* should work */
1071 case VariantAtomic: /* should work except for win condition */
1072 case Variant3Check: /* should work except for win condition */
1073 case VariantShatranj: /* should work except for all win conditions */
1074 case VariantMakruk: /* should work except for daw countdown */
1075 case VariantBerolina: /* might work if TestLegality is off */
1076 case VariantCapaRandom: /* should work */
1077 case VariantJanus: /* should work */
1078 case VariantSuper: /* experimental */
1079 case VariantGreat: /* experimental, requires legality testing to be off */
1080 case VariantSChess: /* S-Chess, should work */
1081 case VariantSpartan: /* should work */
1088 int NextIntegerFromString( char ** str, long * value )
1093 while( *s == ' ' || *s == '\t' ) {
1099 if( *s >= '0' && *s <= '9' ) {
1100 while( *s >= '0' && *s <= '9' ) {
1101 *value = *value * 10 + (*s - '0');
1113 int NextTimeControlFromString( char ** str, long * value )
1116 int result = NextIntegerFromString( str, &temp );
1119 *value = temp * 60; /* Minutes */
1120 if( **str == ':' ) {
1122 result = NextIntegerFromString( str, &temp );
1123 *value += temp; /* Seconds */
1130 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1131 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1132 int result = -1, type = 0; long temp, temp2;
1134 if(**str != ':') return -1; // old params remain in force!
1136 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1137 if( NextIntegerFromString( str, &temp ) ) return -1;
1138 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1141 /* time only: incremental or sudden-death time control */
1142 if(**str == '+') { /* increment follows; read it */
1144 if(**str == '!') type = *(*str)++; // Bronstein TC
1145 if(result = NextIntegerFromString( str, &temp2)) return -1;
1146 *inc = temp2 * 1000;
1147 if(**str == '.') { // read fraction of increment
1148 char *start = ++(*str);
1149 if(result = NextIntegerFromString( str, &temp2)) return -1;
1151 while(start++ < *str) temp2 /= 10;
1155 *moves = 0; *tc = temp * 1000; *incType = type;
1159 (*str)++; /* classical time control */
1160 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1171 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1172 { /* [HGM] get time to add from the multi-session time-control string */
1173 int incType, moves=1; /* kludge to force reading of first session */
1174 long time, increment;
1177 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1178 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1180 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1181 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1182 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1183 if(movenr == -1) return time; /* last move before new session */
1184 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1185 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1186 if(!moves) return increment; /* current session is incremental */
1187 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1188 } while(movenr >= -1); /* try again for next session */
1190 return 0; // no new time quota on this move
1194 ParseTimeControl(tc, ti, mps)
1201 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1204 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1205 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1206 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1210 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1212 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1215 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1217 snprintf(buf, MSG_SIZ, ":%s", mytc);
1219 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1221 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1226 /* Parse second time control */
1229 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1237 timeControl_2 = tc2 * 1000;
1247 timeControl = tc1 * 1000;
1250 timeIncrement = ti * 1000; /* convert to ms */
1251 movesPerSession = 0;
1254 movesPerSession = mps;
1262 if (appData.debugMode) {
1263 fprintf(debugFP, "%s\n", programVersion);
1266 set_cont_sequence(appData.wrapContSeq);
1267 if (appData.matchGames > 0) {
1268 appData.matchMode = TRUE;
1269 } else if (appData.matchMode) {
1270 appData.matchGames = 1;
1272 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1273 appData.matchGames = appData.sameColorGames;
1274 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1275 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1276 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1279 if (appData.noChessProgram || first.protocolVersion == 1) {
1282 /* kludge: allow timeout for initial "feature" commands */
1284 DisplayMessage("", _("Starting chess program"));
1285 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1290 CalculateIndex(int index, int gameNr)
1291 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1293 if(index > 0) return index; // fixed nmber
1294 if(index == 0) return 1;
1295 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1296 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1301 LoadGameOrPosition(int gameNr)
1302 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1303 if (*appData.loadGameFile != NULLCHAR) {
1304 if (!LoadGameFromFile(appData.loadGameFile,
1305 CalculateIndex(appData.loadGameIndex, gameNr),
1306 appData.loadGameFile, FALSE)) {
1307 DisplayFatalError(_("Bad game file"), 0, 1);
1310 } else if (*appData.loadPositionFile != NULLCHAR) {
1311 if (!LoadPositionFromFile(appData.loadPositionFile,
1312 CalculateIndex(appData.loadPositionIndex, gameNr),
1313 appData.loadPositionFile)) {
1314 DisplayFatalError(_("Bad position file"), 0, 1);
1322 ReserveGame(int gameNr, char resChar)
1324 FILE *tf = fopen(appData.tourneyFile, "r+");
1325 char *p, *q, c, buf[MSG_SIZ];
1326 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1327 safeStrCpy(buf, lastMsg, MSG_SIZ);
1328 DisplayMessage(_("Pick new game"), "");
1329 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1330 ParseArgsFromFile(tf);
1331 p = q = appData.results;
1332 if(appData.debugMode) {
1333 char *r = appData.participants;
1334 fprintf(debugFP, "results = '%s'\n", p);
1335 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1336 fprintf(debugFP, "\n");
1338 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1340 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1341 safeStrCpy(q, p, strlen(p) + 2);
1342 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1343 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1344 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1345 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1348 fseek(tf, -(strlen(p)+4), SEEK_END);
1350 if(c != '"') // depending on DOS or Unix line endings we can be one off
1351 fseek(tf, -(strlen(p)+2), SEEK_END);
1352 else fseek(tf, -(strlen(p)+3), SEEK_END);
1353 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1354 DisplayMessage(buf, "");
1355 free(p); appData.results = q;
1356 if(nextGame <= appData.matchGames && resChar != ' ' &&
1357 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1358 UnloadEngine(&first); // next game belongs to other pairing;
1359 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1364 MatchEvent(int mode)
1365 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1367 /* Set up machine vs. machine match */
1369 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1370 if(appData.tourneyFile[0]) {
1372 if(nextGame > appData.matchGames) {
1374 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1375 DisplayError(buf, 0);
1376 appData.tourneyFile[0] = 0;
1380 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1381 DisplayFatalError(_("Can't have a match with no chess programs"),
1386 matchGame = roundNr = 1;
1387 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1392 InitBackEnd3 P((void))
1394 GameMode initialMode;
1398 InitChessProgram(&first, startedFromSetupPosition);
1400 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1401 free(programVersion);
1402 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1403 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1406 if (appData.icsActive) {
1408 /* [DM] Make a console window if needed [HGM] merged ifs */
1414 if (*appData.icsCommPort != NULLCHAR)
1415 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1416 appData.icsCommPort);
1418 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1419 appData.icsHost, appData.icsPort);
1421 if( (len > MSG_SIZ) && appData.debugMode )
1422 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1424 DisplayFatalError(buf, err, 1);
1429 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1431 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1432 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1433 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1434 } else if (appData.noChessProgram) {
1440 if (*appData.cmailGameName != NULLCHAR) {
1442 OpenLoopback(&cmailPR);
1444 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1448 DisplayMessage("", "");
1449 if (StrCaseCmp(appData.initialMode, "") == 0) {
1450 initialMode = BeginningOfGame;
1451 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1452 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1453 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1454 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1457 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1458 initialMode = TwoMachinesPlay;
1459 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1460 initialMode = AnalyzeFile;
1461 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1462 initialMode = AnalyzeMode;
1463 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1464 initialMode = MachinePlaysWhite;
1465 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1466 initialMode = MachinePlaysBlack;
1467 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1468 initialMode = EditGame;
1469 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1470 initialMode = EditPosition;
1471 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1472 initialMode = Training;
1474 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1475 if( (len > MSG_SIZ) && appData.debugMode )
1476 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1478 DisplayFatalError(buf, 0, 2);
1482 if (appData.matchMode) {
1483 if(appData.tourneyFile[0]) { // start tourney from command line
1485 if(f = fopen(appData.tourneyFile, "r")) {
1486 ParseArgsFromFile(f); // make sure tourney parmeters re known
1488 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1491 } else if (*appData.cmailGameName != NULLCHAR) {
1492 /* Set up cmail mode */
1493 ReloadCmailMsgEvent(TRUE);
1495 /* Set up other modes */
1496 if (initialMode == AnalyzeFile) {
1497 if (*appData.loadGameFile == NULLCHAR) {
1498 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1502 if (*appData.loadGameFile != NULLCHAR) {
1503 (void) LoadGameFromFile(appData.loadGameFile,
1504 appData.loadGameIndex,
1505 appData.loadGameFile, TRUE);
1506 } else if (*appData.loadPositionFile != NULLCHAR) {
1507 (void) LoadPositionFromFile(appData.loadPositionFile,
1508 appData.loadPositionIndex,
1509 appData.loadPositionFile);
1510 /* [HGM] try to make self-starting even after FEN load */
1511 /* to allow automatic setup of fairy variants with wtm */
1512 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1513 gameMode = BeginningOfGame;
1514 setboardSpoiledMachineBlack = 1;
1516 /* [HGM] loadPos: make that every new game uses the setup */
1517 /* from file as long as we do not switch variant */
1518 if(!blackPlaysFirst) {
1519 startedFromPositionFile = TRUE;
1520 CopyBoard(filePosition, boards[0]);
1523 if (initialMode == AnalyzeMode) {
1524 if (appData.noChessProgram) {
1525 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1528 if (appData.icsActive) {
1529 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1533 } else if (initialMode == AnalyzeFile) {
1534 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1535 ShowThinkingEvent();
1537 AnalysisPeriodicEvent(1);
1538 } else if (initialMode == MachinePlaysWhite) {
1539 if (appData.noChessProgram) {
1540 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1544 if (appData.icsActive) {
1545 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1549 MachineWhiteEvent();
1550 } else if (initialMode == MachinePlaysBlack) {
1551 if (appData.noChessProgram) {
1552 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1556 if (appData.icsActive) {
1557 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1561 MachineBlackEvent();
1562 } else if (initialMode == TwoMachinesPlay) {
1563 if (appData.noChessProgram) {
1564 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1568 if (appData.icsActive) {
1569 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1574 } else if (initialMode == EditGame) {
1576 } else if (initialMode == EditPosition) {
1577 EditPositionEvent();
1578 } else if (initialMode == Training) {
1579 if (*appData.loadGameFile == NULLCHAR) {
1580 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1589 * Establish will establish a contact to a remote host.port.
1590 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1591 * used to talk to the host.
1592 * Returns 0 if okay, error code if not.
1599 if (*appData.icsCommPort != NULLCHAR) {
1600 /* Talk to the host through a serial comm port */
1601 return OpenCommPort(appData.icsCommPort, &icsPR);
1603 } else if (*appData.gateway != NULLCHAR) {
1604 if (*appData.remoteShell == NULLCHAR) {
1605 /* Use the rcmd protocol to run telnet program on a gateway host */
1606 snprintf(buf, sizeof(buf), "%s %s %s",
1607 appData.telnetProgram, appData.icsHost, appData.icsPort);
1608 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1611 /* Use the rsh program to run telnet program on a gateway host */
1612 if (*appData.remoteUser == NULLCHAR) {
1613 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1614 appData.gateway, appData.telnetProgram,
1615 appData.icsHost, appData.icsPort);
1617 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1618 appData.remoteShell, appData.gateway,
1619 appData.remoteUser, appData.telnetProgram,
1620 appData.icsHost, appData.icsPort);
1622 return StartChildProcess(buf, "", &icsPR);
1625 } else if (appData.useTelnet) {
1626 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1629 /* TCP socket interface differs somewhat between
1630 Unix and NT; handle details in the front end.
1632 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1636 void EscapeExpand(char *p, char *q)
1637 { // [HGM] initstring: routine to shape up string arguments
1638 while(*p++ = *q++) if(p[-1] == '\\')
1640 case 'n': p[-1] = '\n'; break;
1641 case 'r': p[-1] = '\r'; break;
1642 case 't': p[-1] = '\t'; break;
1643 case '\\': p[-1] = '\\'; break;
1644 case 0: *p = 0; return;
1645 default: p[-1] = q[-1]; break;
1650 show_bytes(fp, buf, count)
1656 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1657 fprintf(fp, "\\%03o", *buf & 0xff);
1666 /* Returns an errno value */
1668 OutputMaybeTelnet(pr, message, count, outError)
1674 char buf[8192], *p, *q, *buflim;
1675 int left, newcount, outcount;
1677 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1678 *appData.gateway != NULLCHAR) {
1679 if (appData.debugMode) {
1680 fprintf(debugFP, ">ICS: ");
1681 show_bytes(debugFP, message, count);
1682 fprintf(debugFP, "\n");
1684 return OutputToProcess(pr, message, count, outError);
1687 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1694 if (appData.debugMode) {
1695 fprintf(debugFP, ">ICS: ");
1696 show_bytes(debugFP, buf, newcount);
1697 fprintf(debugFP, "\n");
1699 outcount = OutputToProcess(pr, buf, newcount, outError);
1700 if (outcount < newcount) return -1; /* to be sure */
1707 } else if (((unsigned char) *p) == TN_IAC) {
1708 *q++ = (char) TN_IAC;
1715 if (appData.debugMode) {
1716 fprintf(debugFP, ">ICS: ");
1717 show_bytes(debugFP, buf, newcount);
1718 fprintf(debugFP, "\n");
1720 outcount = OutputToProcess(pr, buf, newcount, outError);
1721 if (outcount < newcount) return -1; /* to be sure */
1726 read_from_player(isr, closure, message, count, error)
1733 int outError, outCount;
1734 static int gotEof = 0;
1736 /* Pass data read from player on to ICS */
1739 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1740 if (outCount < count) {
1741 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1743 } else if (count < 0) {
1744 RemoveInputSource(isr);
1745 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1746 } else if (gotEof++ > 0) {
1747 RemoveInputSource(isr);
1748 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1754 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1755 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1756 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1757 SendToICS("date\n");
1758 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1761 /* added routine for printf style output to ics */
1762 void ics_printf(char *format, ...)
1764 char buffer[MSG_SIZ];
1767 va_start(args, format);
1768 vsnprintf(buffer, sizeof(buffer), format, args);
1769 buffer[sizeof(buffer)-1] = '\0';
1778 int count, outCount, outError;
1780 if (icsPR == NULL) return;
1783 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1784 if (outCount < count) {
1785 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1789 /* This is used for sending logon scripts to the ICS. Sending
1790 without a delay causes problems when using timestamp on ICC
1791 (at least on my machine). */
1793 SendToICSDelayed(s,msdelay)
1797 int count, outCount, outError;
1799 if (icsPR == NULL) return;
1802 if (appData.debugMode) {
1803 fprintf(debugFP, ">ICS: ");
1804 show_bytes(debugFP, s, count);
1805 fprintf(debugFP, "\n");
1807 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1809 if (outCount < count) {
1810 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1815 /* Remove all highlighting escape sequences in s
1816 Also deletes any suffix starting with '('
1819 StripHighlightAndTitle(s)
1822 static char retbuf[MSG_SIZ];
1825 while (*s != NULLCHAR) {
1826 while (*s == '\033') {
1827 while (*s != NULLCHAR && !isalpha(*s)) s++;
1828 if (*s != NULLCHAR) s++;
1830 while (*s != NULLCHAR && *s != '\033') {
1831 if (*s == '(' || *s == '[') {
1842 /* Remove all highlighting escape sequences in s */
1847 static char retbuf[MSG_SIZ];
1850 while (*s != NULLCHAR) {
1851 while (*s == '\033') {
1852 while (*s != NULLCHAR && !isalpha(*s)) s++;
1853 if (*s != NULLCHAR) s++;
1855 while (*s != NULLCHAR && *s != '\033') {
1863 char *variantNames[] = VARIANT_NAMES;
1868 return variantNames[v];
1872 /* Identify a variant from the strings the chess servers use or the
1873 PGN Variant tag names we use. */
1880 VariantClass v = VariantNormal;
1881 int i, found = FALSE;
1887 /* [HGM] skip over optional board-size prefixes */
1888 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1889 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1890 while( *e++ != '_');
1893 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1897 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1898 if (StrCaseStr(e, variantNames[i])) {
1899 v = (VariantClass) i;
1906 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1907 || StrCaseStr(e, "wild/fr")
1908 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1909 v = VariantFischeRandom;
1910 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1911 (i = 1, p = StrCaseStr(e, "w"))) {
1913 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1920 case 0: /* FICS only, actually */
1922 /* Castling legal even if K starts on d-file */
1923 v = VariantWildCastle;
1928 /* Castling illegal even if K & R happen to start in
1929 normal positions. */
1930 v = VariantNoCastle;
1943 /* Castling legal iff K & R start in normal positions */
1949 /* Special wilds for position setup; unclear what to do here */
1950 v = VariantLoadable;
1953 /* Bizarre ICC game */
1954 v = VariantTwoKings;
1957 v = VariantKriegspiel;
1963 v = VariantFischeRandom;
1966 v = VariantCrazyhouse;
1969 v = VariantBughouse;
1975 /* Not quite the same as FICS suicide! */
1976 v = VariantGiveaway;
1982 v = VariantShatranj;
1985 /* Temporary names for future ICC types. The name *will* change in
1986 the next xboard/WinBoard release after ICC defines it. */
2024 v = VariantCapablanca;
2027 v = VariantKnightmate;
2033 v = VariantCylinder;
2039 v = VariantCapaRandom;
2042 v = VariantBerolina;
2054 /* Found "wild" or "w" in the string but no number;
2055 must assume it's normal chess. */
2059 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2060 if( (len > MSG_SIZ) && appData.debugMode )
2061 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2063 DisplayError(buf, 0);
2069 if (appData.debugMode) {
2070 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2071 e, wnum, VariantName(v));
2076 static int leftover_start = 0, leftover_len = 0;
2077 char star_match[STAR_MATCH_N][MSG_SIZ];
2079 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2080 advance *index beyond it, and set leftover_start to the new value of
2081 *index; else return FALSE. If pattern contains the character '*', it
2082 matches any sequence of characters not containing '\r', '\n', or the
2083 character following the '*' (if any), and the matched sequence(s) are
2084 copied into star_match.
2087 looking_at(buf, index, pattern)
2092 char *bufp = &buf[*index], *patternp = pattern;
2094 char *matchp = star_match[0];
2097 if (*patternp == NULLCHAR) {
2098 *index = leftover_start = bufp - buf;
2102 if (*bufp == NULLCHAR) return FALSE;
2103 if (*patternp == '*') {
2104 if (*bufp == *(patternp + 1)) {
2106 matchp = star_match[++star_count];
2110 } else if (*bufp == '\n' || *bufp == '\r') {
2112 if (*patternp == NULLCHAR)
2117 *matchp++ = *bufp++;
2121 if (*patternp != *bufp) return FALSE;
2128 SendToPlayer(data, length)
2132 int error, outCount;
2133 outCount = OutputToProcess(NoProc, data, length, &error);
2134 if (outCount < length) {
2135 DisplayFatalError(_("Error writing to display"), error, 1);
2140 PackHolding(packed, holding)
2152 switch (runlength) {
2163 sprintf(q, "%d", runlength);
2175 /* Telnet protocol requests from the front end */
2177 TelnetRequest(ddww, option)
2178 unsigned char ddww, option;
2180 unsigned char msg[3];
2181 int outCount, outError;
2183 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2185 if (appData.debugMode) {
2186 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2202 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2211 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2214 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2219 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2221 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2228 if (!appData.icsActive) return;
2229 TelnetRequest(TN_DO, TN_ECHO);
2235 if (!appData.icsActive) return;
2236 TelnetRequest(TN_DONT, TN_ECHO);
2240 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2242 /* put the holdings sent to us by the server on the board holdings area */
2243 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2247 if(gameInfo.holdingsWidth < 2) return;
2248 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2249 return; // prevent overwriting by pre-board holdings
2251 if( (int)lowestPiece >= BlackPawn ) {
2254 holdingsStartRow = BOARD_HEIGHT-1;
2257 holdingsColumn = BOARD_WIDTH-1;
2258 countsColumn = BOARD_WIDTH-2;
2259 holdingsStartRow = 0;
2263 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2264 board[i][holdingsColumn] = EmptySquare;
2265 board[i][countsColumn] = (ChessSquare) 0;
2267 while( (p=*holdings++) != NULLCHAR ) {
2268 piece = CharToPiece( ToUpper(p) );
2269 if(piece == EmptySquare) continue;
2270 /*j = (int) piece - (int) WhitePawn;*/
2271 j = PieceToNumber(piece);
2272 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2273 if(j < 0) continue; /* should not happen */
2274 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2275 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2276 board[holdingsStartRow+j*direction][countsColumn]++;
2282 VariantSwitch(Board board, VariantClass newVariant)
2284 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2285 static Board oldBoard;
2287 startedFromPositionFile = FALSE;
2288 if(gameInfo.variant == newVariant) return;
2290 /* [HGM] This routine is called each time an assignment is made to
2291 * gameInfo.variant during a game, to make sure the board sizes
2292 * are set to match the new variant. If that means adding or deleting
2293 * holdings, we shift the playing board accordingly
2294 * This kludge is needed because in ICS observe mode, we get boards
2295 * of an ongoing game without knowing the variant, and learn about the
2296 * latter only later. This can be because of the move list we requested,
2297 * in which case the game history is refilled from the beginning anyway,
2298 * but also when receiving holdings of a crazyhouse game. In the latter
2299 * case we want to add those holdings to the already received position.
2303 if (appData.debugMode) {
2304 fprintf(debugFP, "Switch board from %s to %s\n",
2305 VariantName(gameInfo.variant), VariantName(newVariant));
2306 setbuf(debugFP, NULL);
2308 shuffleOpenings = 0; /* [HGM] shuffle */
2309 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2313 newWidth = 9; newHeight = 9;
2314 gameInfo.holdingsSize = 7;
2315 case VariantBughouse:
2316 case VariantCrazyhouse:
2317 newHoldingsWidth = 2; break;
2321 newHoldingsWidth = 2;
2322 gameInfo.holdingsSize = 8;
2325 case VariantCapablanca:
2326 case VariantCapaRandom:
2329 newHoldingsWidth = gameInfo.holdingsSize = 0;
2332 if(newWidth != gameInfo.boardWidth ||
2333 newHeight != gameInfo.boardHeight ||
2334 newHoldingsWidth != gameInfo.holdingsWidth ) {
2336 /* shift position to new playing area, if needed */
2337 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2338 for(i=0; i<BOARD_HEIGHT; i++)
2339 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2340 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2342 for(i=0; i<newHeight; i++) {
2343 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2344 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2346 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2347 for(i=0; i<BOARD_HEIGHT; i++)
2348 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2349 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2352 gameInfo.boardWidth = newWidth;
2353 gameInfo.boardHeight = newHeight;
2354 gameInfo.holdingsWidth = newHoldingsWidth;
2355 gameInfo.variant = newVariant;
2356 InitDrawingSizes(-2, 0);
2357 } else gameInfo.variant = newVariant;
2358 CopyBoard(oldBoard, board); // remember correctly formatted board
2359 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2360 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2363 static int loggedOn = FALSE;
2365 /*-- Game start info cache: --*/
2367 char gs_kind[MSG_SIZ];
2368 static char player1Name[128] = "";
2369 static char player2Name[128] = "";
2370 static char cont_seq[] = "\n\\ ";
2371 static int player1Rating = -1;
2372 static int player2Rating = -1;
2373 /*----------------------------*/
2375 ColorClass curColor = ColorNormal;
2376 int suppressKibitz = 0;
2379 Boolean soughtPending = FALSE;
2380 Boolean seekGraphUp;
2381 #define MAX_SEEK_ADS 200
2383 char *seekAdList[MAX_SEEK_ADS];
2384 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2385 float tcList[MAX_SEEK_ADS];
2386 char colorList[MAX_SEEK_ADS];
2387 int nrOfSeekAds = 0;
2388 int minRating = 1010, maxRating = 2800;
2389 int hMargin = 10, vMargin = 20, h, w;
2390 extern int squareSize, lineGap;
2395 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2396 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2397 if(r < minRating+100 && r >=0 ) r = minRating+100;
2398 if(r > maxRating) r = maxRating;
2399 if(tc < 1.) tc = 1.;
2400 if(tc > 95.) tc = 95.;
2401 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2402 y = ((double)r - minRating)/(maxRating - minRating)
2403 * (h-vMargin-squareSize/8-1) + vMargin;
2404 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2405 if(strstr(seekAdList[i], " u ")) color = 1;
2406 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2407 !strstr(seekAdList[i], "bullet") &&
2408 !strstr(seekAdList[i], "blitz") &&
2409 !strstr(seekAdList[i], "standard") ) color = 2;
2410 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2411 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2415 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2417 char buf[MSG_SIZ], *ext = "";
2418 VariantClass v = StringToVariant(type);
2419 if(strstr(type, "wild")) {
2420 ext = type + 4; // append wild number
2421 if(v == VariantFischeRandom) type = "chess960"; else
2422 if(v == VariantLoadable) type = "setup"; else
2423 type = VariantName(v);
2425 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2426 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2427 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2428 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2429 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2430 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2431 seekNrList[nrOfSeekAds] = nr;
2432 zList[nrOfSeekAds] = 0;
2433 seekAdList[nrOfSeekAds++] = StrSave(buf);
2434 if(plot) PlotSeekAd(nrOfSeekAds-1);
2441 int x = xList[i], y = yList[i], d=squareSize/4, k;
2442 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2443 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2444 // now replot every dot that overlapped
2445 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2446 int xx = xList[k], yy = yList[k];
2447 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2448 DrawSeekDot(xx, yy, colorList[k]);
2453 RemoveSeekAd(int nr)
2456 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2458 if(seekAdList[i]) free(seekAdList[i]);
2459 seekAdList[i] = seekAdList[--nrOfSeekAds];
2460 seekNrList[i] = seekNrList[nrOfSeekAds];
2461 ratingList[i] = ratingList[nrOfSeekAds];
2462 colorList[i] = colorList[nrOfSeekAds];
2463 tcList[i] = tcList[nrOfSeekAds];
2464 xList[i] = xList[nrOfSeekAds];
2465 yList[i] = yList[nrOfSeekAds];
2466 zList[i] = zList[nrOfSeekAds];
2467 seekAdList[nrOfSeekAds] = NULL;
2473 MatchSoughtLine(char *line)
2475 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2476 int nr, base, inc, u=0; char dummy;
2478 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2479 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2481 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2482 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2483 // match: compact and save the line
2484 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2494 if(!seekGraphUp) return FALSE;
2495 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2496 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2498 DrawSeekBackground(0, 0, w, h);
2499 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2500 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2501 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2502 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2504 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2507 snprintf(buf, MSG_SIZ, "%d", i);
2508 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2511 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2512 for(i=1; i<100; i+=(i<10?1:5)) {
2513 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2514 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2515 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2517 snprintf(buf, MSG_SIZ, "%d", i);
2518 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2521 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2525 int SeekGraphClick(ClickType click, int x, int y, int moving)
2527 static int lastDown = 0, displayed = 0, lastSecond;
2528 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2529 if(click == Release || moving) return FALSE;
2531 soughtPending = TRUE;
2532 SendToICS(ics_prefix);
2533 SendToICS("sought\n"); // should this be "sought all"?
2534 } else { // issue challenge based on clicked ad
2535 int dist = 10000; int i, closest = 0, second = 0;
2536 for(i=0; i<nrOfSeekAds; i++) {
2537 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2538 if(d < dist) { dist = d; closest = i; }
2539 second += (d - zList[i] < 120); // count in-range ads
2540 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2544 second = (second > 1);
2545 if(displayed != closest || second != lastSecond) {
2546 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2547 lastSecond = second; displayed = closest;
2549 if(click == Press) {
2550 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2553 } // on press 'hit', only show info
2554 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2555 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2556 SendToICS(ics_prefix);
2558 return TRUE; // let incoming board of started game pop down the graph
2559 } else if(click == Release) { // release 'miss' is ignored
2560 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2561 if(moving == 2) { // right up-click
2562 nrOfSeekAds = 0; // refresh graph
2563 soughtPending = TRUE;
2564 SendToICS(ics_prefix);
2565 SendToICS("sought\n"); // should this be "sought all"?
2568 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2569 // press miss or release hit 'pop down' seek graph
2570 seekGraphUp = FALSE;
2571 DrawPosition(TRUE, NULL);
2577 read_from_ics(isr, closure, data, count, error)
2584 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2585 #define STARTED_NONE 0
2586 #define STARTED_MOVES 1
2587 #define STARTED_BOARD 2
2588 #define STARTED_OBSERVE 3
2589 #define STARTED_HOLDINGS 4
2590 #define STARTED_CHATTER 5
2591 #define STARTED_COMMENT 6
2592 #define STARTED_MOVES_NOHIDE 7
2594 static int started = STARTED_NONE;
2595 static char parse[20000];
2596 static int parse_pos = 0;
2597 static char buf[BUF_SIZE + 1];
2598 static int firstTime = TRUE, intfSet = FALSE;
2599 static ColorClass prevColor = ColorNormal;
2600 static int savingComment = FALSE;
2601 static int cmatch = 0; // continuation sequence match
2608 int backup; /* [DM] For zippy color lines */
2610 char talker[MSG_SIZ]; // [HGM] chat
2613 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2615 if (appData.debugMode) {
2617 fprintf(debugFP, "<ICS: ");
2618 show_bytes(debugFP, data, count);
2619 fprintf(debugFP, "\n");
2623 if (appData.debugMode) { int f = forwardMostMove;
2624 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2625 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2626 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2629 /* If last read ended with a partial line that we couldn't parse,
2630 prepend it to the new read and try again. */
2631 if (leftover_len > 0) {
2632 for (i=0; i<leftover_len; i++)
2633 buf[i] = buf[leftover_start + i];
2636 /* copy new characters into the buffer */
2637 bp = buf + leftover_len;
2638 buf_len=leftover_len;
2639 for (i=0; i<count; i++)
2642 if (data[i] == '\r')
2645 // join lines split by ICS?
2646 if (!appData.noJoin)
2649 Joining just consists of finding matches against the
2650 continuation sequence, and discarding that sequence
2651 if found instead of copying it. So, until a match
2652 fails, there's nothing to do since it might be the
2653 complete sequence, and thus, something we don't want
2656 if (data[i] == cont_seq[cmatch])
2659 if (cmatch == strlen(cont_seq))
2661 cmatch = 0; // complete match. just reset the counter
2664 it's possible for the ICS to not include the space
2665 at the end of the last word, making our [correct]
2666 join operation fuse two separate words. the server
2667 does this when the space occurs at the width setting.
2669 if (!buf_len || buf[buf_len-1] != ' ')
2680 match failed, so we have to copy what matched before
2681 falling through and copying this character. In reality,
2682 this will only ever be just the newline character, but
2683 it doesn't hurt to be precise.
2685 strncpy(bp, cont_seq, cmatch);
2697 buf[buf_len] = NULLCHAR;
2698 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2703 while (i < buf_len) {
2704 /* Deal with part of the TELNET option negotiation
2705 protocol. We refuse to do anything beyond the
2706 defaults, except that we allow the WILL ECHO option,
2707 which ICS uses to turn off password echoing when we are
2708 directly connected to it. We reject this option
2709 if localLineEditing mode is on (always on in xboard)
2710 and we are talking to port 23, which might be a real
2711 telnet server that will try to keep WILL ECHO on permanently.
2713 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2714 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2715 unsigned char option;
2717 switch ((unsigned char) buf[++i]) {
2719 if (appData.debugMode)
2720 fprintf(debugFP, "\n<WILL ");
2721 switch (option = (unsigned char) buf[++i]) {
2723 if (appData.debugMode)
2724 fprintf(debugFP, "ECHO ");
2725 /* Reply only if this is a change, according
2726 to the protocol rules. */
2727 if (remoteEchoOption) break;
2728 if (appData.localLineEditing &&
2729 atoi(appData.icsPort) == TN_PORT) {
2730 TelnetRequest(TN_DONT, TN_ECHO);
2733 TelnetRequest(TN_DO, TN_ECHO);
2734 remoteEchoOption = TRUE;
2738 if (appData.debugMode)
2739 fprintf(debugFP, "%d ", option);
2740 /* Whatever this is, we don't want it. */
2741 TelnetRequest(TN_DONT, option);
2746 if (appData.debugMode)
2747 fprintf(debugFP, "\n<WONT ");
2748 switch (option = (unsigned char) buf[++i]) {
2750 if (appData.debugMode)
2751 fprintf(debugFP, "ECHO ");
2752 /* Reply only if this is a change, according
2753 to the protocol rules. */
2754 if (!remoteEchoOption) break;
2756 TelnetRequest(TN_DONT, TN_ECHO);
2757 remoteEchoOption = FALSE;
2760 if (appData.debugMode)
2761 fprintf(debugFP, "%d ", (unsigned char) option);
2762 /* Whatever this is, it must already be turned
2763 off, because we never agree to turn on
2764 anything non-default, so according to the
2765 protocol rules, we don't reply. */
2770 if (appData.debugMode)
2771 fprintf(debugFP, "\n<DO ");
2772 switch (option = (unsigned char) buf[++i]) {
2774 /* Whatever this is, we refuse to do it. */
2775 if (appData.debugMode)
2776 fprintf(debugFP, "%d ", option);
2777 TelnetRequest(TN_WONT, option);
2782 if (appData.debugMode)
2783 fprintf(debugFP, "\n<DONT ");
2784 switch (option = (unsigned char) buf[++i]) {
2786 if (appData.debugMode)
2787 fprintf(debugFP, "%d ", option);
2788 /* Whatever this is, we are already not doing
2789 it, because we never agree to do anything
2790 non-default, so according to the protocol
2791 rules, we don't reply. */
2796 if (appData.debugMode)
2797 fprintf(debugFP, "\n<IAC ");
2798 /* Doubled IAC; pass it through */
2802 if (appData.debugMode)
2803 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2804 /* Drop all other telnet commands on the floor */
2807 if (oldi > next_out)
2808 SendToPlayer(&buf[next_out], oldi - next_out);
2814 /* OK, this at least will *usually* work */
2815 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2819 if (loggedOn && !intfSet) {
2820 if (ics_type == ICS_ICC) {
2821 snprintf(str, MSG_SIZ,
2822 "/set-quietly interface %s\n/set-quietly style 12\n",
2824 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2825 strcat(str, "/set-2 51 1\n/set seek 1\n");
2826 } else if (ics_type == ICS_CHESSNET) {
2827 snprintf(str, MSG_SIZ, "/style 12\n");
2829 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2830 strcat(str, programVersion);
2831 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2832 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2833 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2835 strcat(str, "$iset nohighlight 1\n");
2837 strcat(str, "$iset lock 1\n$style 12\n");
2840 NotifyFrontendLogin();
2844 if (started == STARTED_COMMENT) {
2845 /* Accumulate characters in comment */
2846 parse[parse_pos++] = buf[i];
2847 if (buf[i] == '\n') {
2848 parse[parse_pos] = NULLCHAR;
2849 if(chattingPartner>=0) {
2851 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2852 OutputChatMessage(chattingPartner, mess);
2853 chattingPartner = -1;
2854 next_out = i+1; // [HGM] suppress printing in ICS window
2856 if(!suppressKibitz) // [HGM] kibitz
2857 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2858 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2859 int nrDigit = 0, nrAlph = 0, j;
2860 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2861 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2862 parse[parse_pos] = NULLCHAR;
2863 // try to be smart: if it does not look like search info, it should go to
2864 // ICS interaction window after all, not to engine-output window.
2865 for(j=0; j<parse_pos; j++) { // count letters and digits
2866 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2867 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2868 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2870 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2871 int depth=0; float score;
2872 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2873 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2874 pvInfoList[forwardMostMove-1].depth = depth;
2875 pvInfoList[forwardMostMove-1].score = 100*score;
2877 OutputKibitz(suppressKibitz, parse);
2880 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2881 SendToPlayer(tmp, strlen(tmp));
2883 next_out = i+1; // [HGM] suppress printing in ICS window
2885 started = STARTED_NONE;
2887 /* Don't match patterns against characters in comment */
2892 if (started == STARTED_CHATTER) {
2893 if (buf[i] != '\n') {
2894 /* Don't match patterns against characters in chatter */
2898 started = STARTED_NONE;
2899 if(suppressKibitz) next_out = i+1;
2902 /* Kludge to deal with rcmd protocol */
2903 if (firstTime && looking_at(buf, &i, "\001*")) {
2904 DisplayFatalError(&buf[1], 0, 1);
2910 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2913 if (appData.debugMode)
2914 fprintf(debugFP, "ics_type %d\n", ics_type);
2917 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2918 ics_type = ICS_FICS;
2920 if (appData.debugMode)
2921 fprintf(debugFP, "ics_type %d\n", ics_type);
2924 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2925 ics_type = ICS_CHESSNET;
2927 if (appData.debugMode)
2928 fprintf(debugFP, "ics_type %d\n", ics_type);
2933 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2934 looking_at(buf, &i, "Logging you in as \"*\"") ||
2935 looking_at(buf, &i, "will be \"*\""))) {
2936 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2940 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2942 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2943 DisplayIcsInteractionTitle(buf);
2944 have_set_title = TRUE;
2947 /* skip finger notes */
2948 if (started == STARTED_NONE &&
2949 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2950 (buf[i] == '1' && buf[i+1] == '0')) &&
2951 buf[i+2] == ':' && buf[i+3] == ' ') {
2952 started = STARTED_CHATTER;
2958 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2959 if(appData.seekGraph) {
2960 if(soughtPending && MatchSoughtLine(buf+i)) {
2961 i = strstr(buf+i, "rated") - buf;
2962 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2963 next_out = leftover_start = i;
2964 started = STARTED_CHATTER;
2965 suppressKibitz = TRUE;
2968 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2969 && looking_at(buf, &i, "* ads displayed")) {
2970 soughtPending = FALSE;
2975 if(appData.autoRefresh) {
2976 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2977 int s = (ics_type == ICS_ICC); // ICC format differs
2979 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2980 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2981 looking_at(buf, &i, "*% "); // eat prompt
2982 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2983 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2984 next_out = i; // suppress
2987 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2988 char *p = star_match[0];
2990 if(seekGraphUp) RemoveSeekAd(atoi(p));
2991 while(*p && *p++ != ' '); // next
2993 looking_at(buf, &i, "*% "); // eat prompt
2994 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3001 /* skip formula vars */
3002 if (started == STARTED_NONE &&
3003 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3004 started = STARTED_CHATTER;
3009 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3010 if (appData.autoKibitz && started == STARTED_NONE &&
3011 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3012 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3013 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3014 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3015 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3016 suppressKibitz = TRUE;
3017 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3020 && (gameMode == IcsPlayingWhite)) ||
3021 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3022 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3023 started = STARTED_CHATTER; // own kibitz we simply discard
3025 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3026 parse_pos = 0; parse[0] = NULLCHAR;
3027 savingComment = TRUE;
3028 suppressKibitz = gameMode != IcsObserving ? 2 :
3029 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3033 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3034 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3035 && atoi(star_match[0])) {
3036 // suppress the acknowledgements of our own autoKibitz
3038 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3040 SendToPlayer(star_match[0], strlen(star_match[0]));
3041 if(looking_at(buf, &i, "*% ")) // eat prompt
3042 suppressKibitz = FALSE;
3046 } // [HGM] kibitz: end of patch
3048 // [HGM] chat: intercept tells by users for which we have an open chat window
3050 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3051 looking_at(buf, &i, "* whispers:") ||
3052 looking_at(buf, &i, "* kibitzes:") ||
3053 looking_at(buf, &i, "* shouts:") ||
3054 looking_at(buf, &i, "* c-shouts:") ||
3055 looking_at(buf, &i, "--> * ") ||
3056 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3057 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3058 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3059 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3061 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3062 chattingPartner = -1;
3064 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3065 for(p=0; p<MAX_CHAT; p++) {
3066 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3067 talker[0] = '['; strcat(talker, "] ");
3068 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3069 chattingPartner = p; break;
3072 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3073 for(p=0; p<MAX_CHAT; p++) {
3074 if(!strcmp("kibitzes", chatPartner[p])) {
3075 talker[0] = '['; strcat(talker, "] ");
3076 chattingPartner = p; break;
3079 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3080 for(p=0; p<MAX_CHAT; p++) {
3081 if(!strcmp("whispers", chatPartner[p])) {
3082 talker[0] = '['; strcat(talker, "] ");
3083 chattingPartner = p; break;
3086 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3087 if(buf[i-8] == '-' && buf[i-3] == 't')
3088 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3089 if(!strcmp("c-shouts", chatPartner[p])) {
3090 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3091 chattingPartner = p; break;
3094 if(chattingPartner < 0)
3095 for(p=0; p<MAX_CHAT; p++) {
3096 if(!strcmp("shouts", chatPartner[p])) {
3097 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3098 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3099 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3100 chattingPartner = p; break;
3104 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3105 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3106 talker[0] = 0; Colorize(ColorTell, FALSE);
3107 chattingPartner = p; break;
3109 if(chattingPartner<0) i = oldi; else {
3110 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3111 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3112 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113 started = STARTED_COMMENT;
3114 parse_pos = 0; parse[0] = NULLCHAR;
3115 savingComment = 3 + chattingPartner; // counts as TRUE
3116 suppressKibitz = TRUE;
3119 } // [HGM] chat: end of patch
3122 if (appData.zippyTalk || appData.zippyPlay) {
3123 /* [DM] Backup address for color zippy lines */
3125 if (loggedOn == TRUE)
3126 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3127 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3129 } // [DM] 'else { ' deleted
3131 /* Regular tells and says */
3132 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3133 looking_at(buf, &i, "* (your partner) tells you: ") ||
3134 looking_at(buf, &i, "* says: ") ||
3135 /* Don't color "message" or "messages" output */
3136 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3137 looking_at(buf, &i, "*. * at *:*: ") ||
3138 looking_at(buf, &i, "--* (*:*): ") ||
3139 /* Message notifications (same color as tells) */
3140 looking_at(buf, &i, "* has left a message ") ||
3141 looking_at(buf, &i, "* just sent you a message:\n") ||
3142 /* Whispers and kibitzes */
3143 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3144 looking_at(buf, &i, "* kibitzes: ") ||
3146 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3148 if (tkind == 1 && strchr(star_match[0], ':')) {
3149 /* Avoid "tells you:" spoofs in channels */
3152 if (star_match[0][0] == NULLCHAR ||
3153 strchr(star_match[0], ' ') ||
3154 (tkind == 3 && strchr(star_match[1], ' '))) {
3155 /* Reject bogus matches */
3158 if (appData.colorize) {
3159 if (oldi > next_out) {
3160 SendToPlayer(&buf[next_out], oldi - next_out);
3165 Colorize(ColorTell, FALSE);
3166 curColor = ColorTell;
3169 Colorize(ColorKibitz, FALSE);
3170 curColor = ColorKibitz;
3173 p = strrchr(star_match[1], '(');
3180 Colorize(ColorChannel1, FALSE);
3181 curColor = ColorChannel1;
3183 Colorize(ColorChannel, FALSE);
3184 curColor = ColorChannel;
3188 curColor = ColorNormal;
3192 if (started == STARTED_NONE && appData.autoComment &&
3193 (gameMode == IcsObserving ||
3194 gameMode == IcsPlayingWhite ||
3195 gameMode == IcsPlayingBlack)) {
3196 parse_pos = i - oldi;
3197 memcpy(parse, &buf[oldi], parse_pos);
3198 parse[parse_pos] = NULLCHAR;
3199 started = STARTED_COMMENT;
3200 savingComment = TRUE;
3202 started = STARTED_CHATTER;
3203 savingComment = FALSE;
3210 if (looking_at(buf, &i, "* s-shouts: ") ||
3211 looking_at(buf, &i, "* c-shouts: ")) {
3212 if (appData.colorize) {
3213 if (oldi > next_out) {
3214 SendToPlayer(&buf[next_out], oldi - next_out);
3217 Colorize(ColorSShout, FALSE);
3218 curColor = ColorSShout;
3221 started = STARTED_CHATTER;
3225 if (looking_at(buf, &i, "--->")) {
3230 if (looking_at(buf, &i, "* shouts: ") ||
3231 looking_at(buf, &i, "--> ")) {
3232 if (appData.colorize) {
3233 if (oldi > next_out) {
3234 SendToPlayer(&buf[next_out], oldi - next_out);
3237 Colorize(ColorShout, FALSE);
3238 curColor = ColorShout;
3241 started = STARTED_CHATTER;
3245 if (looking_at( buf, &i, "Challenge:")) {
3246 if (appData.colorize) {
3247 if (oldi > next_out) {
3248 SendToPlayer(&buf[next_out], oldi - next_out);
3251 Colorize(ColorChallenge, FALSE);
3252 curColor = ColorChallenge;
3258 if (looking_at(buf, &i, "* offers you") ||
3259 looking_at(buf, &i, "* offers to be") ||
3260 looking_at(buf, &i, "* would like to") ||
3261 looking_at(buf, &i, "* requests to") ||
3262 looking_at(buf, &i, "Your opponent offers") ||
3263 looking_at(buf, &i, "Your opponent requests")) {
3265 if (appData.colorize) {
3266 if (oldi > next_out) {
3267 SendToPlayer(&buf[next_out], oldi - next_out);
3270 Colorize(ColorRequest, FALSE);
3271 curColor = ColorRequest;
3276 if (looking_at(buf, &i, "* (*) seeking")) {
3277 if (appData.colorize) {
3278 if (oldi > next_out) {
3279 SendToPlayer(&buf[next_out], oldi - next_out);
3282 Colorize(ColorSeek, FALSE);
3283 curColor = ColorSeek;
3288 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3290 if (looking_at(buf, &i, "\\ ")) {
3291 if (prevColor != ColorNormal) {
3292 if (oldi > next_out) {
3293 SendToPlayer(&buf[next_out], oldi - next_out);
3296 Colorize(prevColor, TRUE);
3297 curColor = prevColor;
3299 if (savingComment) {
3300 parse_pos = i - oldi;
3301 memcpy(parse, &buf[oldi], parse_pos);
3302 parse[parse_pos] = NULLCHAR;
3303 started = STARTED_COMMENT;
3304 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3305 chattingPartner = savingComment - 3; // kludge to remember the box
3307 started = STARTED_CHATTER;
3312 if (looking_at(buf, &i, "Black Strength :") ||
3313 looking_at(buf, &i, "<<< style 10 board >>>") ||
3314 looking_at(buf, &i, "<10>") ||
3315 looking_at(buf, &i, "#@#")) {
3316 /* Wrong board style */
3318 SendToICS(ics_prefix);
3319 SendToICS("set style 12\n");
3320 SendToICS(ics_prefix);
3321 SendToICS("refresh\n");
3325 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3327 have_sent_ICS_logon = 1;
3331 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3332 (looking_at(buf, &i, "\n<12> ") ||
3333 looking_at(buf, &i, "<12> "))) {
3335 if (oldi > next_out) {
3336 SendToPlayer(&buf[next_out], oldi - next_out);
3339 started = STARTED_BOARD;
3344 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3345 looking_at(buf, &i, "<b1> ")) {
3346 if (oldi > next_out) {
3347 SendToPlayer(&buf[next_out], oldi - next_out);
3350 started = STARTED_HOLDINGS;
3355 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3357 /* Header for a move list -- first line */
3359 switch (ics_getting_history) {
3363 case BeginningOfGame:
3364 /* User typed "moves" or "oldmoves" while we
3365 were idle. Pretend we asked for these
3366 moves and soak them up so user can step
3367 through them and/or save them.
3370 gameMode = IcsObserving;
3373 ics_getting_history = H_GOT_UNREQ_HEADER;
3375 case EditGame: /*?*/
3376 case EditPosition: /*?*/
3377 /* Should above feature work in these modes too? */
3378 /* For now it doesn't */
3379 ics_getting_history = H_GOT_UNWANTED_HEADER;
3382 ics_getting_history = H_GOT_UNWANTED_HEADER;
3387 /* Is this the right one? */
3388 if (gameInfo.white && gameInfo.black &&
3389 strcmp(gameInfo.white, star_match[0]) == 0 &&
3390 strcmp(gameInfo.black, star_match[2]) == 0) {
3392 ics_getting_history = H_GOT_REQ_HEADER;
3395 case H_GOT_REQ_HEADER:
3396 case H_GOT_UNREQ_HEADER:
3397 case H_GOT_UNWANTED_HEADER:
3398 case H_GETTING_MOVES:
3399 /* Should not happen */
3400 DisplayError(_("Error gathering move list: two headers"), 0);
3401 ics_getting_history = H_FALSE;
3405 /* Save player ratings into gameInfo if needed */
3406 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3407 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3408 (gameInfo.whiteRating == -1 ||
3409 gameInfo.blackRating == -1)) {
3411 gameInfo.whiteRating = string_to_rating(star_match[1]);
3412 gameInfo.blackRating = string_to_rating(star_match[3]);
3413 if (appData.debugMode)
3414 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3415 gameInfo.whiteRating, gameInfo.blackRating);
3420 if (looking_at(buf, &i,
3421 "* * match, initial time: * minute*, increment: * second")) {
3422 /* Header for a move list -- second line */
3423 /* Initial board will follow if this is a wild game */
3424 if (gameInfo.event != NULL) free(gameInfo.event);
3425 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3426 gameInfo.event = StrSave(str);
3427 /* [HGM] we switched variant. Translate boards if needed. */
3428 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3432 if (looking_at(buf, &i, "Move ")) {
3433 /* Beginning of a move list */
3434 switch (ics_getting_history) {
3436 /* Normally should not happen */
3437 /* Maybe user hit reset while we were parsing */
3440 /* Happens if we are ignoring a move list that is not
3441 * the one we just requested. Common if the user
3442 * tries to observe two games without turning off
3445 case H_GETTING_MOVES:
3446 /* Should not happen */
3447 DisplayError(_("Error gathering move list: nested"), 0);
3448 ics_getting_history = H_FALSE;
3450 case H_GOT_REQ_HEADER:
3451 ics_getting_history = H_GETTING_MOVES;
3452 started = STARTED_MOVES;
3454 if (oldi > next_out) {
3455 SendToPlayer(&buf[next_out], oldi - next_out);
3458 case H_GOT_UNREQ_HEADER:
3459 ics_getting_history = H_GETTING_MOVES;
3460 started = STARTED_MOVES_NOHIDE;
3463 case H_GOT_UNWANTED_HEADER:
3464 ics_getting_history = H_FALSE;
3470 if (looking_at(buf, &i, "% ") ||
3471 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3472 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3473 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3474 soughtPending = FALSE;
3478 if(suppressKibitz) next_out = i;
3479 savingComment = FALSE;
3483 case STARTED_MOVES_NOHIDE:
3484 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3485 parse[parse_pos + i - oldi] = NULLCHAR;
3486 ParseGameHistory(parse);
3488 if (appData.zippyPlay && first.initDone) {
3489 FeedMovesToProgram(&first, forwardMostMove);
3490 if (gameMode == IcsPlayingWhite) {
3491 if (WhiteOnMove(forwardMostMove)) {
3492 if (first.sendTime) {
3493 if (first.useColors) {
3494 SendToProgram("black\n", &first);
3496 SendTimeRemaining(&first, TRUE);
3498 if (first.useColors) {
3499 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3501 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3502 first.maybeThinking = TRUE;
3504 if (first.usePlayother) {
3505 if (first.sendTime) {
3506 SendTimeRemaining(&first, TRUE);
3508 SendToProgram("playother\n", &first);
3514 } else if (gameMode == IcsPlayingBlack) {
3515 if (!WhiteOnMove(forwardMostMove)) {
3516 if (first.sendTime) {
3517 if (first.useColors) {
3518 SendToProgram("white\n", &first);
3520 SendTimeRemaining(&first, FALSE);
3522 if (first.useColors) {
3523 SendToProgram("black\n", &first);
3525 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3526 first.maybeThinking = TRUE;
3528 if (first.usePlayother) {
3529 if (first.sendTime) {
3530 SendTimeRemaining(&first, FALSE);
3532 SendToProgram("playother\n", &first);
3541 if (gameMode == IcsObserving && ics_gamenum == -1) {
3542 /* Moves came from oldmoves or moves command
3543 while we weren't doing anything else.
3545 currentMove = forwardMostMove;
3546 ClearHighlights();/*!!could figure this out*/
3547 flipView = appData.flipView;
3548 DrawPosition(TRUE, boards[currentMove]);
3549 DisplayBothClocks();
3550 snprintf(str, MSG_SIZ, "%s vs. %s",
3551 gameInfo.white, gameInfo.black);
3555 /* Moves were history of an active game */
3556 if (gameInfo.resultDetails != NULL) {
3557 free(gameInfo.resultDetails);
3558 gameInfo.resultDetails = NULL;
3561 HistorySet(parseList, backwardMostMove,
3562 forwardMostMove, currentMove-1);
3563 DisplayMove(currentMove - 1);
3564 if (started == STARTED_MOVES) next_out = i;
3565 started = STARTED_NONE;
3566 ics_getting_history = H_FALSE;
3569 case STARTED_OBSERVE:
3570 started = STARTED_NONE;
3571 SendToICS(ics_prefix);
3572 SendToICS("refresh\n");
3578 if(bookHit) { // [HGM] book: simulate book reply
3579 static char bookMove[MSG_SIZ]; // a bit generous?
3581 programStats.nodes = programStats.depth = programStats.time =
3582 programStats.score = programStats.got_only_move = 0;
3583 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3585 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3586 strcat(bookMove, bookHit);
3587 HandleMachineMove(bookMove, &first);
3592 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3593 started == STARTED_HOLDINGS ||
3594 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3595 /* Accumulate characters in move list or board */
3596 parse[parse_pos++] = buf[i];
3599 /* Start of game messages. Mostly we detect start of game
3600 when the first board image arrives. On some versions
3601 of the ICS, though, we need to do a "refresh" after starting
3602 to observe in order to get the current board right away. */
3603 if (looking_at(buf, &i, "Adding game * to observation list")) {
3604 started = STARTED_OBSERVE;
3608 /* Handle auto-observe */
3609 if (appData.autoObserve &&
3610 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3611 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3613 /* Choose the player that was highlighted, if any. */
3614 if (star_match[0][0] == '\033' ||
3615 star_match[1][0] != '\033') {
3616 player = star_match[0];
3618 player = star_match[2];
3620 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3621 ics_prefix, StripHighlightAndTitle(player));
3624 /* Save ratings from notify string */
3625 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3626 player1Rating = string_to_rating(star_match[1]);
3627 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3628 player2Rating = string_to_rating(star_match[3]);
3630 if (appData.debugMode)
3632 "Ratings from 'Game notification:' %s %d, %s %d\n",
3633 player1Name, player1Rating,
3634 player2Name, player2Rating);
3639 /* Deal with automatic examine mode after a game,
3640 and with IcsObserving -> IcsExamining transition */
3641 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3642 looking_at(buf, &i, "has made you an examiner of game *")) {
3644 int gamenum = atoi(star_match[0]);
3645 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3646 gamenum == ics_gamenum) {
3647 /* We were already playing or observing this game;
3648 no need to refetch history */
3649 gameMode = IcsExamining;
3651 pauseExamForwardMostMove = forwardMostMove;
3652 } else if (currentMove < forwardMostMove) {
3653 ForwardInner(forwardMostMove);
3656 /* I don't think this case really can happen */
3657 SendToICS(ics_prefix);
3658 SendToICS("refresh\n");
3663 /* Error messages */
3664 // if (ics_user_moved) {
3665 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3666 if (looking_at(buf, &i, "Illegal move") ||
3667 looking_at(buf, &i, "Not a legal move") ||
3668 looking_at(buf, &i, "Your king is in check") ||
3669 looking_at(buf, &i, "It isn't your turn") ||
3670 looking_at(buf, &i, "It is not your move")) {
3672 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3673 currentMove = forwardMostMove-1;
3674 DisplayMove(currentMove - 1); /* before DMError */
3675 DrawPosition(FALSE, boards[currentMove]);
3676 SwitchClocks(forwardMostMove-1); // [HGM] race
3677 DisplayBothClocks();
3679 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3685 if (looking_at(buf, &i, "still have time") ||
3686 looking_at(buf, &i, "not out of time") ||
3687 looking_at(buf, &i, "either player is out of time") ||
3688 looking_at(buf, &i, "has timeseal; checking")) {
3689 /* We must have called his flag a little too soon */
3690 whiteFlag = blackFlag = FALSE;
3694 if (looking_at(buf, &i, "added * seconds to") ||
3695 looking_at(buf, &i, "seconds were added to")) {
3696 /* Update the clocks */
3697 SendToICS(ics_prefix);
3698 SendToICS("refresh\n");
3702 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3703 ics_clock_paused = TRUE;
3708 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3709 ics_clock_paused = FALSE;
3714 /* Grab player ratings from the Creating: message.
3715 Note we have to check for the special case when
3716 the ICS inserts things like [white] or [black]. */
3717 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3718 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3720 0 player 1 name (not necessarily white)
3722 2 empty, white, or black (IGNORED)
3723 3 player 2 name (not necessarily black)
3726 The names/ratings are sorted out when the game
3727 actually starts (below).
3729 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3730 player1Rating = string_to_rating(star_match[1]);
3731 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3732 player2Rating = string_to_rating(star_match[4]);
3734 if (appData.debugMode)
3736 "Ratings from 'Creating:' %s %d, %s %d\n",
3737 player1Name, player1Rating,
3738 player2Name, player2Rating);
3743 /* Improved generic start/end-of-game messages */
3744 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3745 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3746 /* If tkind == 0: */
3747 /* star_match[0] is the game number */
3748 /* [1] is the white player's name */
3749 /* [2] is the black player's name */
3750 /* For end-of-game: */
3751 /* [3] is the reason for the game end */
3752 /* [4] is a PGN end game-token, preceded by " " */
3753 /* For start-of-game: */
3754 /* [3] begins with "Creating" or "Continuing" */
3755 /* [4] is " *" or empty (don't care). */
3756 int gamenum = atoi(star_match[0]);
3757 char *whitename, *blackname, *why, *endtoken;
3758 ChessMove endtype = EndOfFile;
3761 whitename = star_match[1];
3762 blackname = star_match[2];
3763 why = star_match[3];
3764 endtoken = star_match[4];
3766 whitename = star_match[1];
3767 blackname = star_match[3];
3768 why = star_match[5];
3769 endtoken = star_match[6];
3772 /* Game start messages */
3773 if (strncmp(why, "Creating ", 9) == 0 ||
3774 strncmp(why, "Continuing ", 11) == 0) {
3775 gs_gamenum = gamenum;
3776 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3777 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3779 if (appData.zippyPlay) {
3780 ZippyGameStart(whitename, blackname);
3783 partnerBoardValid = FALSE; // [HGM] bughouse
3787 /* Game end messages */
3788 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3789 ics_gamenum != gamenum) {
3792 while (endtoken[0] == ' ') endtoken++;
3793 switch (endtoken[0]) {
3796 endtype = GameUnfinished;
3799 endtype = BlackWins;
3802 if (endtoken[1] == '/')
3803 endtype = GameIsDrawn;
3805 endtype = WhiteWins;
3808 GameEnds(endtype, why, GE_ICS);
3810 if (appData.zippyPlay && first.initDone) {
3811 ZippyGameEnd(endtype, why);
3812 if (first.pr == NULL) {
3813 /* Start the next process early so that we'll
3814 be ready for the next challenge */
3815 StartChessProgram(&first);
3817 /* Send "new" early, in case this command takes
3818 a long time to finish, so that we'll be ready
3819 for the next challenge. */
3820 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3824 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3828 if (looking_at(buf, &i, "Removing game * from observation") ||
3829 looking_at(buf, &i, "no longer observing game *") ||
3830 looking_at(buf, &i, "Game * (*) has no examiners")) {
3831 if (gameMode == IcsObserving &&
3832 atoi(star_match[0]) == ics_gamenum)
3834 /* icsEngineAnalyze */
3835 if (appData.icsEngineAnalyze) {
3842 ics_user_moved = FALSE;
3847 if (looking_at(buf, &i, "no longer examining game *")) {
3848 if (gameMode == IcsExamining &&
3849 atoi(star_match[0]) == ics_gamenum)
3853 ics_user_moved = FALSE;
3858 /* Advance leftover_start past any newlines we find,
3859 so only partial lines can get reparsed */
3860 if (looking_at(buf, &i, "\n")) {
3861 prevColor = curColor;
3862 if (curColor != ColorNormal) {
3863 if (oldi > next_out) {
3864 SendToPlayer(&buf[next_out], oldi - next_out);
3867 Colorize(ColorNormal, FALSE);
3868 curColor = ColorNormal;
3870 if (started == STARTED_BOARD) {
3871 started = STARTED_NONE;
3872 parse[parse_pos] = NULLCHAR;
3873 ParseBoard12(parse);
3876 /* Send premove here */
3877 if (appData.premove) {
3879 if (currentMove == 0 &&
3880 gameMode == IcsPlayingWhite &&
3881 appData.premoveWhite) {
3882 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3883 if (appData.debugMode)
3884 fprintf(debugFP, "Sending premove:\n");
3886 } else if (currentMove == 1 &&
3887 gameMode == IcsPlayingBlack &&
3888 appData.premoveBlack) {
3889 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3890 if (appData.debugMode)
3891 fprintf(debugFP, "Sending premove:\n");
3893 } else if (gotPremove) {
3895 ClearPremoveHighlights();
3896 if (appData.debugMode)
3897 fprintf(debugFP, "Sending premove:\n");
3898 UserMoveEvent(premoveFromX, premoveFromY,
3899 premoveToX, premoveToY,
3904 /* Usually suppress following prompt */
3905 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3906 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3907 if (looking_at(buf, &i, "*% ")) {
3908 savingComment = FALSE;
3913 } else if (started == STARTED_HOLDINGS) {
3915 char new_piece[MSG_SIZ];
3916 started = STARTED_NONE;
3917 parse[parse_pos] = NULLCHAR;
3918 if (appData.debugMode)
3919 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3920 parse, currentMove);
3921 if (sscanf(parse, " game %d", &gamenum) == 1) {
3922 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3923 if (gameInfo.variant == VariantNormal) {
3924 /* [HGM] We seem to switch variant during a game!
3925 * Presumably no holdings were displayed, so we have
3926 * to move the position two files to the right to
3927 * create room for them!
3929 VariantClass newVariant;
3930 switch(gameInfo.boardWidth) { // base guess on board width
3931 case 9: newVariant = VariantShogi; break;
3932 case 10: newVariant = VariantGreat; break;
3933 default: newVariant = VariantCrazyhouse; break;
3935 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3936 /* Get a move list just to see the header, which
3937 will tell us whether this is really bug or zh */
3938 if (ics_getting_history == H_FALSE) {
3939 ics_getting_history = H_REQUESTED;
3940 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3944 new_piece[0] = NULLCHAR;
3945 sscanf(parse, "game %d white [%s black [%s <- %s",
3946 &gamenum, white_holding, black_holding,
3948 white_holding[strlen(white_holding)-1] = NULLCHAR;
3949 black_holding[strlen(black_holding)-1] = NULLCHAR;
3950 /* [HGM] copy holdings to board holdings area */
3951 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3952 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3953 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3955 if (appData.zippyPlay && first.initDone) {
3956 ZippyHoldings(white_holding, black_holding,
3960 if (tinyLayout || smallLayout) {
3961 char wh[16], bh[16];
3962 PackHolding(wh, white_holding);
3963 PackHolding(bh, black_holding);
3964 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3965 gameInfo.white, gameInfo.black);
3967 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3968 gameInfo.white, white_holding,
3969 gameInfo.black, black_holding);
3971 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3972 DrawPosition(FALSE, boards[currentMove]);
3974 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3975 sscanf(parse, "game %d white [%s black [%s <- %s",
3976 &gamenum, white_holding, black_holding,
3978 white_holding[strlen(white_holding)-1] = NULLCHAR;
3979 black_holding[strlen(black_holding)-1] = NULLCHAR;
3980 /* [HGM] copy holdings to partner-board holdings area */
3981 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3982 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3983 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3984 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3985 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3988 /* Suppress following prompt */
3989 if (looking_at(buf, &i, "*% ")) {
3990 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3991 savingComment = FALSE;
3999 i++; /* skip unparsed character and loop back */
4002 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4003 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4004 // SendToPlayer(&buf[next_out], i - next_out);
4005 started != STARTED_HOLDINGS && leftover_start > next_out) {
4006 SendToPlayer(&buf[next_out], leftover_start - next_out);
4010 leftover_len = buf_len - leftover_start;
4011 /* if buffer ends with something we couldn't parse,
4012 reparse it after appending the next read */
4014 } else if (count == 0) {
4015 RemoveInputSource(isr);
4016 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4018 DisplayFatalError(_("Error reading from ICS"), error, 1);
4023 /* Board style 12 looks like this:
4025 <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
4027 * The "<12> " is stripped before it gets to this routine. The two
4028 * trailing 0's (flip state and clock ticking) are later addition, and
4029 * some chess servers may not have them, or may have only the first.
4030 * Additional trailing fields may be added in the future.
4033 #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"
4035 #define RELATION_OBSERVING_PLAYED 0
4036 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4037 #define RELATION_PLAYING_MYMOVE 1
4038 #define RELATION_PLAYING_NOTMYMOVE -1
4039 #define RELATION_EXAMINING 2
4040 #define RELATION_ISOLATED_BOARD -3
4041 #define RELATION_STARTING_POSITION -4 /* FICS only */
4044 ParseBoard12(string)
4047 GameMode newGameMode;
4048 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4049 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4050 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4051 char to_play, board_chars[200];
4052 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4053 char black[32], white[32];
4055 int prevMove = currentMove;
4058 int fromX, fromY, toX, toY;
4060 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4061 char *bookHit = NULL; // [HGM] book
4062 Boolean weird = FALSE, reqFlag = FALSE;
4064 fromX = fromY = toX = toY = -1;
4068 if (appData.debugMode)
4069 fprintf(debugFP, _("Parsing board: %s\n"), string);
4071 move_str[0] = NULLCHAR;
4072 elapsed_time[0] = NULLCHAR;
4073 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4075 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4076 if(string[i] == ' ') { ranks++; files = 0; }
4078 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4081 for(j = 0; j <i; j++) board_chars[j] = string[j];
4082 board_chars[i] = '\0';
4085 n = sscanf(string, PATTERN, &to_play, &double_push,
4086 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4087 &gamenum, white, black, &relation, &basetime, &increment,
4088 &white_stren, &black_stren, &white_time, &black_time,
4089 &moveNum, str, elapsed_time, move_str, &ics_flip,
4093 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4094 DisplayError(str, 0);
4098 /* Convert the move number to internal form */
4099 moveNum = (moveNum - 1) * 2;
4100 if (to_play == 'B') moveNum++;
4101 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4102 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4108 case RELATION_OBSERVING_PLAYED:
4109 case RELATION_OBSERVING_STATIC:
4110 if (gamenum == -1) {
4111 /* Old ICC buglet */
4112 relation = RELATION_OBSERVING_STATIC;
4114 newGameMode = IcsObserving;
4116 case RELATION_PLAYING_MYMOVE:
4117 case RELATION_PLAYING_NOTMYMOVE:
4119 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4120 IcsPlayingWhite : IcsPlayingBlack;
4122 case RELATION_EXAMINING:
4123 newGameMode = IcsExamining;
4125 case RELATION_ISOLATED_BOARD:
4127 /* Just display this board. If user was doing something else,
4128 we will forget about it until the next board comes. */
4129 newGameMode = IcsIdle;
4131 case RELATION_STARTING_POSITION:
4132 newGameMode = gameMode;
4136 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4137 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4138 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4140 for (k = 0; k < ranks; k++) {
4141 for (j = 0; j < files; j++)
4142 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143 if(gameInfo.holdingsWidth > 1) {
4144 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4148 CopyBoard(partnerBoard, board);
4149 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4150 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4151 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4152 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4153 if(toSqr = strchr(str, '-')) {
4154 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4155 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4156 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4157 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4158 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4159 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4160 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4161 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4162 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4163 DisplayMessage(partnerStatus, "");
4164 partnerBoardValid = TRUE;
4168 /* Modify behavior for initial board display on move listing
4171 switch (ics_getting_history) {
4175 case H_GOT_REQ_HEADER:
4176 case H_GOT_UNREQ_HEADER:
4177 /* This is the initial position of the current game */
4178 gamenum = ics_gamenum;
4179 moveNum = 0; /* old ICS bug workaround */
4180 if (to_play == 'B') {
4181 startedFromSetupPosition = TRUE;
4182 blackPlaysFirst = TRUE;
4184 if (forwardMostMove == 0) forwardMostMove = 1;
4185 if (backwardMostMove == 0) backwardMostMove = 1;
4186 if (currentMove == 0) currentMove = 1;
4188 newGameMode = gameMode;
4189 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4191 case H_GOT_UNWANTED_HEADER:
4192 /* This is an initial board that we don't want */
4194 case H_GETTING_MOVES:
4195 /* Should not happen */
4196 DisplayError(_("Error gathering move list: extra board"), 0);
4197 ics_getting_history = H_FALSE;
4201 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4202 weird && (int)gameInfo.variant < (int)VariantShogi) {
4203 /* [HGM] We seem to have switched variant unexpectedly
4204 * Try to guess new variant from board size
4206 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4207 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4208 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4209 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4210 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4211 if(!weird) newVariant = VariantNormal;
4212 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4213 /* Get a move list just to see the header, which
4214 will tell us whether this is really bug or zh */
4215 if (ics_getting_history == H_FALSE) {
4216 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4217 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4222 /* Take action if this is the first board of a new game, or of a
4223 different game than is currently being displayed. */
4224 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4225 relation == RELATION_ISOLATED_BOARD) {
4227 /* Forget the old game and get the history (if any) of the new one */
4228 if (gameMode != BeginningOfGame) {
4232 if (appData.autoRaiseBoard) BoardToTop();
4234 if (gamenum == -1) {
4235 newGameMode = IcsIdle;
4236 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4237 appData.getMoveList && !reqFlag) {
4238 /* Need to get game history */
4239 ics_getting_history = H_REQUESTED;
4240 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4244 /* Initially flip the board to have black on the bottom if playing
4245 black or if the ICS flip flag is set, but let the user change
4246 it with the Flip View button. */
4247 flipView = appData.autoFlipView ?
4248 (newGameMode == IcsPlayingBlack) || ics_flip :
4251 /* Done with values from previous mode; copy in new ones */
4252 gameMode = newGameMode;
4254 ics_gamenum = gamenum;
4255 if (gamenum == gs_gamenum) {
4256 int klen = strlen(gs_kind);
4257 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4258 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4259 gameInfo.event = StrSave(str);
4261 gameInfo.event = StrSave("ICS game");
4263 gameInfo.site = StrSave(appData.icsHost);
4264 gameInfo.date = PGNDate();
4265 gameInfo.round = StrSave("-");
4266 gameInfo.white = StrSave(white);
4267 gameInfo.black = StrSave(black);
4268 timeControl = basetime * 60 * 1000;
4270 timeIncrement = increment * 1000;
4271 movesPerSession = 0;
4272 gameInfo.timeControl = TimeControlTagValue();
4273 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4274 if (appData.debugMode) {
4275 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4276 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4277 setbuf(debugFP, NULL);
4280 gameInfo.outOfBook = NULL;
4282 /* Do we have the ratings? */
4283 if (strcmp(player1Name, white) == 0 &&
4284 strcmp(player2Name, black) == 0) {
4285 if (appData.debugMode)
4286 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4287 player1Rating, player2Rating);
4288 gameInfo.whiteRating = player1Rating;
4289 gameInfo.blackRating = player2Rating;
4290 } else if (strcmp(player2Name, white) == 0 &&
4291 strcmp(player1Name, black) == 0) {
4292 if (appData.debugMode)
4293 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4294 player2Rating, player1Rating);
4295 gameInfo.whiteRating = player2Rating;
4296 gameInfo.blackRating = player1Rating;
4298 player1Name[0] = player2Name[0] = NULLCHAR;
4300 /* Silence shouts if requested */
4301 if (appData.quietPlay &&
4302 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4303 SendToICS(ics_prefix);
4304 SendToICS("set shout 0\n");
4308 /* Deal with midgame name changes */
4310 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4311 if (gameInfo.white) free(gameInfo.white);
4312 gameInfo.white = StrSave(white);
4314 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4315 if (gameInfo.black) free(gameInfo.black);
4316 gameInfo.black = StrSave(black);
4320 /* Throw away game result if anything actually changes in examine mode */
4321 if (gameMode == IcsExamining && !newGame) {
4322 gameInfo.result = GameUnfinished;
4323 if (gameInfo.resultDetails != NULL) {
4324 free(gameInfo.resultDetails);
4325 gameInfo.resultDetails = NULL;
4329 /* In pausing && IcsExamining mode, we ignore boards coming
4330 in if they are in a different variation than we are. */
4331 if (pauseExamInvalid) return;
4332 if (pausing && gameMode == IcsExamining) {
4333 if (moveNum <= pauseExamForwardMostMove) {
4334 pauseExamInvalid = TRUE;
4335 forwardMostMove = pauseExamForwardMostMove;
4340 if (appData.debugMode) {
4341 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4343 /* Parse the board */
4344 for (k = 0; k < ranks; k++) {
4345 for (j = 0; j < files; j++)
4346 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4347 if(gameInfo.holdingsWidth > 1) {
4348 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4349 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4352 CopyBoard(boards[moveNum], board);
4353 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4355 startedFromSetupPosition =
4356 !CompareBoards(board, initialPosition);
4357 if(startedFromSetupPosition)
4358 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4361 /* [HGM] Set castling rights. Take the outermost Rooks,
4362 to make it also work for FRC opening positions. Note that board12
4363 is really defective for later FRC positions, as it has no way to
4364 indicate which Rook can castle if they are on the same side of King.
4365 For the initial position we grant rights to the outermost Rooks,
4366 and remember thos rights, and we then copy them on positions
4367 later in an FRC game. This means WB might not recognize castlings with
4368 Rooks that have moved back to their original position as illegal,
4369 but in ICS mode that is not its job anyway.
4371 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4372 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4374 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4375 if(board[0][i] == WhiteRook) j = i;
4376 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4377 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4378 if(board[0][i] == WhiteRook) j = i;
4379 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4380 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4381 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4382 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4383 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4384 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4385 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4387 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4388 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4389 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4390 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4391 if(board[BOARD_HEIGHT-1][k] == bKing)
4392 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4393 if(gameInfo.variant == VariantTwoKings) {
4394 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4395 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4396 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4399 r = boards[moveNum][CASTLING][0] = initialRights[0];
4400 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4401 r = boards[moveNum][CASTLING][1] = initialRights[1];
4402 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4403 r = boards[moveNum][CASTLING][3] = initialRights[3];
4404 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4405 r = boards[moveNum][CASTLING][4] = initialRights[4];
4406 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4407 /* wildcastle kludge: always assume King has rights */
4408 r = boards[moveNum][CASTLING][2] = initialRights[2];
4409 r = boards[moveNum][CASTLING][5] = initialRights[5];
4411 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4412 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4415 if (ics_getting_history == H_GOT_REQ_HEADER ||
4416 ics_getting_history == H_GOT_UNREQ_HEADER) {
4417 /* This was an initial position from a move list, not
4418 the current position */
4422 /* Update currentMove and known move number limits */
4423 newMove = newGame || moveNum > forwardMostMove;
4426 forwardMostMove = backwardMostMove = currentMove = moveNum;
4427 if (gameMode == IcsExamining && moveNum == 0) {
4428 /* Workaround for ICS limitation: we are not told the wild
4429 type when starting to examine a game. But if we ask for
4430 the move list, the move list header will tell us */
4431 ics_getting_history = H_REQUESTED;
4432 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4435 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4436 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4438 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4439 /* [HGM] applied this also to an engine that is silently watching */
4440 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4441 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4442 gameInfo.variant == currentlyInitializedVariant) {
4443 takeback = forwardMostMove - moveNum;
4444 for (i = 0; i < takeback; i++) {
4445 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4446 SendToProgram("undo\n", &first);
4451 forwardMostMove = moveNum;
4452 if (!pausing || currentMove > forwardMostMove)
4453 currentMove = forwardMostMove;
4455 /* New part of history that is not contiguous with old part */
4456 if (pausing && gameMode == IcsExamining) {
4457 pauseExamInvalid = TRUE;
4458 forwardMostMove = pauseExamForwardMostMove;
4461 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4463 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4464 // [HGM] when we will receive the move list we now request, it will be
4465 // fed to the engine from the first move on. So if the engine is not
4466 // in the initial position now, bring it there.
4467 InitChessProgram(&first, 0);
4470 ics_getting_history = H_REQUESTED;
4471 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4474 forwardMostMove = backwardMostMove = currentMove = moveNum;
4477 /* Update the clocks */
4478 if (strchr(elapsed_time, '.')) {
4480 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4481 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4483 /* Time is in seconds */
4484 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4485 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4490 if (appData.zippyPlay && newGame &&
4491 gameMode != IcsObserving && gameMode != IcsIdle &&
4492 gameMode != IcsExamining)
4493 ZippyFirstBoard(moveNum, basetime, increment);
4496 /* Put the move on the move list, first converting
4497 to canonical algebraic form. */
4499 if (appData.debugMode) {
4500 if (appData.debugMode) { int f = forwardMostMove;
4501 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4502 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4503 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4505 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4506 fprintf(debugFP, "moveNum = %d\n", moveNum);
4507 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4508 setbuf(debugFP, NULL);
4510 if (moveNum <= backwardMostMove) {
4511 /* We don't know what the board looked like before
4513 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4514 strcat(parseList[moveNum - 1], " ");
4515 strcat(parseList[moveNum - 1], elapsed_time);
4516 moveList[moveNum - 1][0] = NULLCHAR;
4517 } else if (strcmp(move_str, "none") == 0) {
4518 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4519 /* Again, we don't know what the board looked like;
4520 this is really the start of the game. */
4521 parseList[moveNum - 1][0] = NULLCHAR;
4522 moveList[moveNum - 1][0] = NULLCHAR;
4523 backwardMostMove = moveNum;
4524 startedFromSetupPosition = TRUE;
4525 fromX = fromY = toX = toY = -1;
4527 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4528 // So we parse the long-algebraic move string in stead of the SAN move
4529 int valid; char buf[MSG_SIZ], *prom;
4531 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4532 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4533 // str looks something like "Q/a1-a2"; kill the slash
4535 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4536 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4537 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4538 strcat(buf, prom); // long move lacks promo specification!
4539 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4540 if(appData.debugMode)
4541 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4542 safeStrCpy(move_str, buf, MSG_SIZ);
4544 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4545 &fromX, &fromY, &toX, &toY, &promoChar)
4546 || ParseOneMove(buf, moveNum - 1, &moveType,
4547 &fromX, &fromY, &toX, &toY, &promoChar);
4548 // end of long SAN patch
4550 (void) CoordsToAlgebraic(boards[moveNum - 1],
4551 PosFlags(moveNum - 1),
4552 fromY, fromX, toY, toX, promoChar,
4553 parseList[moveNum-1]);
4554 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4560 if(gameInfo.variant != VariantShogi)
4561 strcat(parseList[moveNum - 1], "+");
4564 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4565 strcat(parseList[moveNum - 1], "#");
4568 strcat(parseList[moveNum - 1], " ");
4569 strcat(parseList[moveNum - 1], elapsed_time);
4570 /* currentMoveString is set as a side-effect of ParseOneMove */
4571 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4572 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4573 strcat(moveList[moveNum - 1], "\n");
4575 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4576 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4577 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4578 ChessSquare old, new = boards[moveNum][k][j];
4579 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4580 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4581 if(old == new) continue;
4582 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4583 else if(new == WhiteWazir || new == BlackWazir) {
4584 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4585 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4586 else boards[moveNum][k][j] = old; // preserve type of Gold
4587 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4588 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4591 /* Move from ICS was illegal!? Punt. */
4592 if (appData.debugMode) {
4593 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4594 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4596 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4597 strcat(parseList[moveNum - 1], " ");
4598 strcat(parseList[moveNum - 1], elapsed_time);
4599 moveList[moveNum - 1][0] = NULLCHAR;
4600 fromX = fromY = toX = toY = -1;
4603 if (appData.debugMode) {
4604 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4605 setbuf(debugFP, NULL);
4609 /* Send move to chess program (BEFORE animating it). */
4610 if (appData.zippyPlay && !newGame && newMove &&
4611 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4613 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4614 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4615 if (moveList[moveNum - 1][0] == NULLCHAR) {
4616 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4618 DisplayError(str, 0);
4620 if (first.sendTime) {
4621 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4623 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4624 if (firstMove && !bookHit) {
4626 if (first.useColors) {
4627 SendToProgram(gameMode == IcsPlayingWhite ?
4629 "black\ngo\n", &first);
4631 SendToProgram("go\n", &first);
4633 first.maybeThinking = TRUE;
4636 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4637 if (moveList[moveNum - 1][0] == NULLCHAR) {
4638 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4639 DisplayError(str, 0);
4641 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4642 SendMoveToProgram(moveNum - 1, &first);
4649 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4650 /* If move comes from a remote source, animate it. If it
4651 isn't remote, it will have already been animated. */
4652 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4653 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4655 if (!pausing && appData.highlightLastMove) {
4656 SetHighlights(fromX, fromY, toX, toY);
4660 /* Start the clocks */
4661 whiteFlag = blackFlag = FALSE;
4662 appData.clockMode = !(basetime == 0 && increment == 0);
4664 ics_clock_paused = TRUE;
4666 } else if (ticking == 1) {
4667 ics_clock_paused = FALSE;
4669 if (gameMode == IcsIdle ||
4670 relation == RELATION_OBSERVING_STATIC ||
4671 relation == RELATION_EXAMINING ||
4673 DisplayBothClocks();
4677 /* Display opponents and material strengths */
4678 if (gameInfo.variant != VariantBughouse &&
4679 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4680 if (tinyLayout || smallLayout) {
4681 if(gameInfo.variant == VariantNormal)
4682 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4683 gameInfo.white, white_stren, gameInfo.black, black_stren,
4684 basetime, increment);
4686 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4687 gameInfo.white, white_stren, gameInfo.black, black_stren,
4688 basetime, increment, (int) gameInfo.variant);
4690 if(gameInfo.variant == VariantNormal)
4691 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4692 gameInfo.white, white_stren, gameInfo.black, black_stren,
4693 basetime, increment);
4695 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4696 gameInfo.white, white_stren, gameInfo.black, black_stren,
4697 basetime, increment, VariantName(gameInfo.variant));
4700 if (appData.debugMode) {
4701 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4706 /* Display the board */
4707 if (!pausing && !appData.noGUI) {
4709 if (appData.premove)
4711 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4712 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4713 ClearPremoveHighlights();
4715 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4716 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4717 DrawPosition(j, boards[currentMove]);
4719 DisplayMove(moveNum - 1);
4720 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4721 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4722 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4723 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4727 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4729 if(bookHit) { // [HGM] book: simulate book reply
4730 static char bookMove[MSG_SIZ]; // a bit generous?
4732 programStats.nodes = programStats.depth = programStats.time =
4733 programStats.score = programStats.got_only_move = 0;
4734 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4736 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4737 strcat(bookMove, bookHit);
4738 HandleMachineMove(bookMove, &first);
4747 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4748 ics_getting_history = H_REQUESTED;
4749 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4755 AnalysisPeriodicEvent(force)
4758 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4759 && !force) || !appData.periodicUpdates)
4762 /* Send . command to Crafty to collect stats */
4763 SendToProgram(".\n", &first);
4765 /* Don't send another until we get a response (this makes
4766 us stop sending to old Crafty's which don't understand
4767 the "." command (sending illegal cmds resets node count & time,
4768 which looks bad)) */
4769 programStats.ok_to_send = 0;
4772 void ics_update_width(new_width)
4775 ics_printf("set width %d\n", new_width);
4779 SendMoveToProgram(moveNum, cps)
4781 ChessProgramState *cps;
4785 if (cps->useUsermove) {
4786 SendToProgram("usermove ", cps);
4790 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4791 int len = space - parseList[moveNum];
4792 memcpy(buf, parseList[moveNum], len);
4794 buf[len] = NULLCHAR;
4796 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4798 SendToProgram(buf, cps);
4800 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4801 AlphaRank(moveList[moveNum], 4);
4802 SendToProgram(moveList[moveNum], cps);
4803 AlphaRank(moveList[moveNum], 4); // and back
4805 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4806 * the engine. It would be nice to have a better way to identify castle
4808 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4809 && cps->useOOCastle) {
4810 int fromX = moveList[moveNum][0] - AAA;
4811 int fromY = moveList[moveNum][1] - ONE;
4812 int toX = moveList[moveNum][2] - AAA;
4813 int toY = moveList[moveNum][3] - ONE;
4814 if((boards[moveNum][fromY][fromX] == WhiteKing
4815 && boards[moveNum][toY][toX] == WhiteRook)
4816 || (boards[moveNum][fromY][fromX] == BlackKing
4817 && boards[moveNum][toY][toX] == BlackRook)) {
4818 if(toX > fromX) SendToProgram("O-O\n", cps);
4819 else SendToProgram("O-O-O\n", cps);
4821 else SendToProgram(moveList[moveNum], cps);
4823 else SendToProgram(moveList[moveNum], cps);
4824 /* End of additions by Tord */
4827 /* [HGM] setting up the opening has brought engine in force mode! */
4828 /* Send 'go' if we are in a mode where machine should play. */
4829 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4830 (gameMode == TwoMachinesPlay ||
4832 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4834 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4835 SendToProgram("go\n", cps);
4836 if (appData.debugMode) {
4837 fprintf(debugFP, "(extra)\n");
4840 setboardSpoiledMachineBlack = 0;
4844 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4846 int fromX, fromY, toX, toY;
4849 char user_move[MSG_SIZ];
4853 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4854 (int)moveType, fromX, fromY, toX, toY);
4855 DisplayError(user_move + strlen("say "), 0);
4857 case WhiteKingSideCastle:
4858 case BlackKingSideCastle:
4859 case WhiteQueenSideCastleWild:
4860 case BlackQueenSideCastleWild:
4862 case WhiteHSideCastleFR:
4863 case BlackHSideCastleFR:
4865 snprintf(user_move, MSG_SIZ, "o-o\n");
4867 case WhiteQueenSideCastle:
4868 case BlackQueenSideCastle:
4869 case WhiteKingSideCastleWild:
4870 case BlackKingSideCastleWild:
4872 case WhiteASideCastleFR:
4873 case BlackASideCastleFR:
4875 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4877 case WhiteNonPromotion:
4878 case BlackNonPromotion:
4879 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4881 case WhitePromotion:
4882 case BlackPromotion:
4883 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4884 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4885 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4886 PieceToChar(WhiteFerz));
4887 else if(gameInfo.variant == VariantGreat)
4888 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4889 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4890 PieceToChar(WhiteMan));
4892 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4893 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4899 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4900 ToUpper(PieceToChar((ChessSquare) fromX)),
4901 AAA + toX, ONE + toY);
4903 case IllegalMove: /* could be a variant we don't quite understand */
4904 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4906 case WhiteCapturesEnPassant:
4907 case BlackCapturesEnPassant:
4908 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4909 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4912 SendToICS(user_move);
4913 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4914 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4919 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4920 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4921 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4922 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4923 DisplayError("You cannot do this while you are playing or observing", 0);
4926 if(gameMode != IcsExamining) { // is this ever not the case?
4927 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4929 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4930 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4931 } else { // on FICS we must first go to general examine mode
4932 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4934 if(gameInfo.variant != VariantNormal) {
4935 // try figure out wild number, as xboard names are not always valid on ICS
4936 for(i=1; i<=36; i++) {
4937 snprintf(buf, MSG_SIZ, "wild/%d", i);
4938 if(StringToVariant(buf) == gameInfo.variant) break;
4940 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4941 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4942 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4943 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4944 SendToICS(ics_prefix);
4946 if(startedFromSetupPosition || backwardMostMove != 0) {
4947 fen = PositionToFEN(backwardMostMove, NULL);
4948 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4949 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4951 } else { // FICS: everything has to set by separate bsetup commands
4952 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4953 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4955 if(!WhiteOnMove(backwardMostMove)) {
4956 SendToICS("bsetup tomove black\n");
4958 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4959 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4961 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4962 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4964 i = boards[backwardMostMove][EP_STATUS];
4965 if(i >= 0) { // set e.p.
4966 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4972 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4973 SendToICS("bsetup done\n"); // switch to normal examining.
4975 for(i = backwardMostMove; i<last; i++) {
4977 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4980 SendToICS(ics_prefix);
4981 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4985 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4990 if (rf == DROP_RANK) {
4991 sprintf(move, "%c@%c%c\n",
4992 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4994 if (promoChar == 'x' || promoChar == NULLCHAR) {
4995 sprintf(move, "%c%c%c%c\n",
4996 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4998 sprintf(move, "%c%c%c%c%c\n",
4999 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5005 ProcessICSInitScript(f)
5010 while (fgets(buf, MSG_SIZ, f)) {
5011 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5018 static int lastX, lastY, selectFlag, dragging;
5023 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5024 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5025 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5026 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5027 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5028 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5031 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5032 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5033 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5034 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5036 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5037 appData.testLegality && (promoSweep == king ||
5038 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5039 ChangeDragPiece(promoSweep);
5042 int PromoScroll(int x, int y)
5046 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5047 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5048 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5049 if(!step) return FALSE;
5050 lastX = x; lastY = y;
5051 if((promoSweep < BlackPawn) == flipView) step = -step;
5052 if(step > 0) selectFlag = 1;
5053 if(!selectFlag) Sweep(step);
5060 ChessSquare piece = boards[currentMove][toY][toX];
5063 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5064 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5065 if(!step) step = -1;
5066 } while(PieceToChar(pieceSweep) == '.');
5067 boards[currentMove][toY][toX] = pieceSweep;
5068 DrawPosition(FALSE, boards[currentMove]);
5069 boards[currentMove][toY][toX] = piece;
5071 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5073 AlphaRank(char *move, int n)
5075 // char *p = move, c; int x, y;
5077 if (appData.debugMode) {
5078 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5082 move[2]>='0' && move[2]<='9' &&
5083 move[3]>='a' && move[3]<='x' ) {
5085 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5086 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5088 if(move[0]>='0' && move[0]<='9' &&
5089 move[1]>='a' && move[1]<='x' &&
5090 move[2]>='0' && move[2]<='9' &&
5091 move[3]>='a' && move[3]<='x' ) {
5092 /* input move, Shogi -> normal */
5093 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5094 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5095 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5096 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5099 move[3]>='0' && move[3]<='9' &&
5100 move[2]>='a' && move[2]<='x' ) {
5102 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5103 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5106 move[0]>='a' && move[0]<='x' &&
5107 move[3]>='0' && move[3]<='9' &&
5108 move[2]>='a' && move[2]<='x' ) {
5109 /* output move, normal -> Shogi */
5110 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5111 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5112 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5113 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5114 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5116 if (appData.debugMode) {
5117 fprintf(debugFP, " out = '%s'\n", move);
5121 char yy_textstr[8000];
5123 /* Parser for moves from gnuchess, ICS, or user typein box */
5125 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5128 ChessMove *moveType;
5129 int *fromX, *fromY, *toX, *toY;
5132 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5134 switch (*moveType) {
5135 case WhitePromotion:
5136 case BlackPromotion:
5137 case WhiteNonPromotion:
5138 case BlackNonPromotion:
5140 case WhiteCapturesEnPassant:
5141 case BlackCapturesEnPassant:
5142 case WhiteKingSideCastle:
5143 case WhiteQueenSideCastle:
5144 case BlackKingSideCastle:
5145 case BlackQueenSideCastle:
5146 case WhiteKingSideCastleWild:
5147 case WhiteQueenSideCastleWild:
5148 case BlackKingSideCastleWild:
5149 case BlackQueenSideCastleWild:
5150 /* Code added by Tord: */
5151 case WhiteHSideCastleFR:
5152 case WhiteASideCastleFR:
5153 case BlackHSideCastleFR:
5154 case BlackASideCastleFR:
5155 /* End of code added by Tord */
5156 case IllegalMove: /* bug or odd chess variant */
5157 *fromX = currentMoveString[0] - AAA;
5158 *fromY = currentMoveString[1] - ONE;
5159 *toX = currentMoveString[2] - AAA;
5160 *toY = currentMoveString[3] - ONE;
5161 *promoChar = currentMoveString[4];
5162 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5163 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5164 if (appData.debugMode) {
5165 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5167 *fromX = *fromY = *toX = *toY = 0;
5170 if (appData.testLegality) {
5171 return (*moveType != IllegalMove);
5173 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5174 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5179 *fromX = *moveType == WhiteDrop ?
5180 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5181 (int) CharToPiece(ToLower(currentMoveString[0]));
5183 *toX = currentMoveString[2] - AAA;
5184 *toY = currentMoveString[3] - ONE;
5185 *promoChar = NULLCHAR;
5189 case ImpossibleMove:
5199 if (appData.debugMode) {
5200 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5203 *fromX = *fromY = *toX = *toY = 0;
5204 *promoChar = NULLCHAR;
5211 ParsePV(char *pv, Boolean storeComments)
5212 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5213 int fromX, fromY, toX, toY; char promoChar;
5218 endPV = forwardMostMove;
5220 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5221 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5222 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5223 if(appData.debugMode){
5224 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5226 if(!valid && nr == 0 &&
5227 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5228 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5229 // Hande case where played move is different from leading PV move
5230 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5231 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5232 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5233 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5234 endPV += 2; // if position different, keep this
5235 moveList[endPV-1][0] = fromX + AAA;
5236 moveList[endPV-1][1] = fromY + ONE;
5237 moveList[endPV-1][2] = toX + AAA;
5238 moveList[endPV-1][3] = toY + ONE;
5239 parseList[endPV-1][0] = NULLCHAR;
5240 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5243 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5244 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5245 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5246 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5247 valid++; // allow comments in PV
5251 if(endPV+1 > framePtr) break; // no space, truncate
5254 CopyBoard(boards[endPV], boards[endPV-1]);
5255 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5256 moveList[endPV-1][0] = fromX + AAA;
5257 moveList[endPV-1][1] = fromY + ONE;
5258 moveList[endPV-1][2] = toX + AAA;
5259 moveList[endPV-1][3] = toY + ONE;
5260 moveList[endPV-1][4] = promoChar;
5261 moveList[endPV-1][5] = NULLCHAR;
5262 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5264 CoordsToAlgebraic(boards[endPV - 1],
5265 PosFlags(endPV - 1),
5266 fromY, fromX, toY, toX, promoChar,
5267 parseList[endPV - 1]);
5269 parseList[endPV-1][0] = NULLCHAR;
5271 currentMove = endPV;
5272 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5273 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5274 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5275 DrawPosition(TRUE, boards[currentMove]);
5279 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5284 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5285 lastX = x; lastY = y;
5286 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5288 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5289 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5291 do{ while(buf[index] && buf[index] != '\n') index++;
5292 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5294 ParsePV(buf+startPV, FALSE);
5295 *start = startPV; *end = index-1;
5300 LoadPV(int x, int y)
5301 { // called on right mouse click to load PV
5302 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5303 lastX = x; lastY = y;
5304 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5311 if(endPV < 0) return;
5313 currentMove = forwardMostMove;
5314 ClearPremoveHighlights();
5315 DrawPosition(TRUE, boards[currentMove]);
5319 MovePV(int x, int y, int h)
5320 { // step through PV based on mouse coordinates (called on mouse move)
5321 int margin = h>>3, step = 0;
5323 // we must somehow check if right button is still down (might be released off board!)
5324 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5325 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5326 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5328 lastX = x; lastY = y;
5330 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5331 if(endPV < 0) return;
5332 if(y < margin) step = 1; else
5333 if(y > h - margin) step = -1;
5334 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5335 currentMove += step;
5336 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5337 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5338 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5339 DrawPosition(FALSE, boards[currentMove]);
5343 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5344 // All positions will have equal probability, but the current method will not provide a unique
5345 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5351 int piecesLeft[(int)BlackPawn];
5352 int seed, nrOfShuffles;
5354 void GetPositionNumber()
5355 { // sets global variable seed
5358 seed = appData.defaultFrcPosition;
5359 if(seed < 0) { // randomize based on time for negative FRC position numbers
5360 for(i=0; i<50; i++) seed += random();
5361 seed = random() ^ random() >> 8 ^ random() << 8;
5362 if(seed<0) seed = -seed;
5366 int put(Board board, int pieceType, int rank, int n, int shade)
5367 // put the piece on the (n-1)-th empty squares of the given shade
5371 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5372 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5373 board[rank][i] = (ChessSquare) pieceType;
5374 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5376 piecesLeft[pieceType]--;
5384 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5385 // calculate where the next piece goes, (any empty square), and put it there
5389 i = seed % squaresLeft[shade];
5390 nrOfShuffles *= squaresLeft[shade];
5391 seed /= squaresLeft[shade];
5392 put(board, pieceType, rank, i, shade);
5395 void AddTwoPieces(Board board, int pieceType, int rank)
5396 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5398 int i, n=squaresLeft[ANY], j=n-1, k;
5400 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5401 i = seed % k; // pick one
5404 while(i >= j) i -= j--;
5405 j = n - 1 - j; i += j;
5406 put(board, pieceType, rank, j, ANY);
5407 put(board, pieceType, rank, i, ANY);
5410 void SetUpShuffle(Board board, int number)
5414 GetPositionNumber(); nrOfShuffles = 1;
5416 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5417 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5418 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5420 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5422 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5423 p = (int) board[0][i];
5424 if(p < (int) BlackPawn) piecesLeft[p] ++;
5425 board[0][i] = EmptySquare;
5428 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5429 // shuffles restricted to allow normal castling put KRR first
5430 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5431 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5432 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5433 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5434 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5435 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5436 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5437 put(board, WhiteRook, 0, 0, ANY);
5438 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5441 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5442 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5443 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5444 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5445 while(piecesLeft[p] >= 2) {
5446 AddOnePiece(board, p, 0, LITE);
5447 AddOnePiece(board, p, 0, DARK);
5449 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5452 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5453 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5454 // but we leave King and Rooks for last, to possibly obey FRC restriction
5455 if(p == (int)WhiteRook) continue;
5456 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5457 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5460 // now everything is placed, except perhaps King (Unicorn) and Rooks
5462 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5463 // Last King gets castling rights
5464 while(piecesLeft[(int)WhiteUnicorn]) {
5465 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5466 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5469 while(piecesLeft[(int)WhiteKing]) {
5470 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5471 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5476 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5477 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5480 // Only Rooks can be left; simply place them all
5481 while(piecesLeft[(int)WhiteRook]) {
5482 i = put(board, WhiteRook, 0, 0, ANY);
5483 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5486 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5488 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5491 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5492 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5495 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5498 int SetCharTable( char *table, const char * map )
5499 /* [HGM] moved here from winboard.c because of its general usefulness */
5500 /* Basically a safe strcpy that uses the last character as King */
5502 int result = FALSE; int NrPieces;
5504 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5505 && NrPieces >= 12 && !(NrPieces&1)) {
5506 int i; /* [HGM] Accept even length from 12 to 34 */
5508 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5509 for( i=0; i<NrPieces/2-1; i++ ) {
5511 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5513 table[(int) WhiteKing] = map[NrPieces/2-1];
5514 table[(int) BlackKing] = map[NrPieces-1];
5522 void Prelude(Board board)
5523 { // [HGM] superchess: random selection of exo-pieces
5524 int i, j, k; ChessSquare p;
5525 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5527 GetPositionNumber(); // use FRC position number
5529 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5530 SetCharTable(pieceToChar, appData.pieceToCharTable);
5531 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5532 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5535 j = seed%4; seed /= 4;
5536 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5537 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5538 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5539 j = seed%3 + (seed%3 >= j); seed /= 3;
5540 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5541 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5542 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5543 j = seed%3; seed /= 3;
5544 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5545 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5546 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5547 j = seed%2 + (seed%2 >= j); seed /= 2;
5548 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5549 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5550 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5551 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5552 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5553 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5554 put(board, exoPieces[0], 0, 0, ANY);
5555 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5559 InitPosition(redraw)
5562 ChessSquare (* pieces)[BOARD_FILES];
5563 int i, j, pawnRow, overrule,
5564 oldx = gameInfo.boardWidth,
5565 oldy = gameInfo.boardHeight,
5566 oldh = gameInfo.holdingsWidth;
5569 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5571 /* [AS] Initialize pv info list [HGM] and game status */
5573 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5574 pvInfoList[i].depth = 0;
5575 boards[i][EP_STATUS] = EP_NONE;
5576 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5579 initialRulePlies = 0; /* 50-move counter start */
5581 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5582 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5586 /* [HGM] logic here is completely changed. In stead of full positions */
5587 /* the initialized data only consist of the two backranks. The switch */
5588 /* selects which one we will use, which is than copied to the Board */
5589 /* initialPosition, which for the rest is initialized by Pawns and */
5590 /* empty squares. This initial position is then copied to boards[0], */
5591 /* possibly after shuffling, so that it remains available. */
5593 gameInfo.holdingsWidth = 0; /* default board sizes */
5594 gameInfo.boardWidth = 8;
5595 gameInfo.boardHeight = 8;
5596 gameInfo.holdingsSize = 0;
5597 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5598 for(i=0; i<BOARD_FILES-2; i++)
5599 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5600 initialPosition[EP_STATUS] = EP_NONE;
5601 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5602 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5603 SetCharTable(pieceNickName, appData.pieceNickNames);
5604 else SetCharTable(pieceNickName, "............");
5607 switch (gameInfo.variant) {
5608 case VariantFischeRandom:
5609 shuffleOpenings = TRUE;
5612 case VariantShatranj:
5613 pieces = ShatranjArray;
5614 nrCastlingRights = 0;
5615 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5618 pieces = makrukArray;
5619 nrCastlingRights = 0;
5620 startedFromSetupPosition = TRUE;
5621 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5623 case VariantTwoKings:
5624 pieces = twoKingsArray;
5626 case VariantCapaRandom:
5627 shuffleOpenings = TRUE;
5628 case VariantCapablanca:
5629 pieces = CapablancaArray;
5630 gameInfo.boardWidth = 10;
5631 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5634 pieces = GothicArray;
5635 gameInfo.boardWidth = 10;
5636 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5639 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5640 gameInfo.holdingsSize = 7;
5643 pieces = JanusArray;
5644 gameInfo.boardWidth = 10;
5645 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5646 nrCastlingRights = 6;
5647 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5648 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5649 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5650 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5651 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5652 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5655 pieces = FalconArray;
5656 gameInfo.boardWidth = 10;
5657 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5659 case VariantXiangqi:
5660 pieces = XiangqiArray;
5661 gameInfo.boardWidth = 9;
5662 gameInfo.boardHeight = 10;
5663 nrCastlingRights = 0;
5664 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5667 pieces = ShogiArray;
5668 gameInfo.boardWidth = 9;
5669 gameInfo.boardHeight = 9;
5670 gameInfo.holdingsSize = 7;
5671 nrCastlingRights = 0;
5672 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5674 case VariantCourier:
5675 pieces = CourierArray;
5676 gameInfo.boardWidth = 12;
5677 nrCastlingRights = 0;
5678 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5680 case VariantKnightmate:
5681 pieces = KnightmateArray;
5682 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5684 case VariantSpartan:
5685 pieces = SpartanArray;
5686 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5689 pieces = fairyArray;
5690 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5693 pieces = GreatArray;
5694 gameInfo.boardWidth = 10;
5695 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5696 gameInfo.holdingsSize = 8;
5700 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5701 gameInfo.holdingsSize = 8;
5702 startedFromSetupPosition = TRUE;
5704 case VariantCrazyhouse:
5705 case VariantBughouse:
5707 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5708 gameInfo.holdingsSize = 5;
5710 case VariantWildCastle:
5712 /* !!?shuffle with kings guaranteed to be on d or e file */
5713 shuffleOpenings = 1;
5715 case VariantNoCastle:
5717 nrCastlingRights = 0;
5718 /* !!?unconstrained back-rank shuffle */
5719 shuffleOpenings = 1;
5724 if(appData.NrFiles >= 0) {
5725 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5726 gameInfo.boardWidth = appData.NrFiles;
5728 if(appData.NrRanks >= 0) {
5729 gameInfo.boardHeight = appData.NrRanks;
5731 if(appData.holdingsSize >= 0) {
5732 i = appData.holdingsSize;
5733 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5734 gameInfo.holdingsSize = i;
5736 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5737 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5738 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5740 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5741 if(pawnRow < 1) pawnRow = 1;
5742 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5744 /* User pieceToChar list overrules defaults */
5745 if(appData.pieceToCharTable != NULL)
5746 SetCharTable(pieceToChar, appData.pieceToCharTable);
5748 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5750 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5751 s = (ChessSquare) 0; /* account holding counts in guard band */
5752 for( i=0; i<BOARD_HEIGHT; i++ )
5753 initialPosition[i][j] = s;
5755 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5756 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5757 initialPosition[pawnRow][j] = WhitePawn;
5758 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5759 if(gameInfo.variant == VariantXiangqi) {
5761 initialPosition[pawnRow][j] =
5762 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5763 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5764 initialPosition[2][j] = WhiteCannon;
5765 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5769 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5771 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5774 initialPosition[1][j] = WhiteBishop;
5775 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5777 initialPosition[1][j] = WhiteRook;
5778 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5781 if( nrCastlingRights == -1) {
5782 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5783 /* This sets default castling rights from none to normal corners */
5784 /* Variants with other castling rights must set them themselves above */
5785 nrCastlingRights = 6;
5787 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5788 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5789 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5790 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5791 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5792 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5795 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5796 if(gameInfo.variant == VariantGreat) { // promotion commoners
5797 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5798 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5799 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5800 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5802 if( gameInfo.variant == VariantSChess ) {
5803 initialPosition[1][0] = BlackMarshall;
5804 initialPosition[2][0] = BlackAngel;
5805 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5806 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5807 initialPosition[1][1] = initialPosition[2][1] =
5808 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5810 if (appData.debugMode) {
5811 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5813 if(shuffleOpenings) {
5814 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5815 startedFromSetupPosition = TRUE;
5817 if(startedFromPositionFile) {
5818 /* [HGM] loadPos: use PositionFile for every new game */
5819 CopyBoard(initialPosition, filePosition);
5820 for(i=0; i<nrCastlingRights; i++)
5821 initialRights[i] = filePosition[CASTLING][i];
5822 startedFromSetupPosition = TRUE;
5825 CopyBoard(boards[0], initialPosition);
5827 if(oldx != gameInfo.boardWidth ||
5828 oldy != gameInfo.boardHeight ||
5829 oldv != gameInfo.variant ||
5830 oldh != gameInfo.holdingsWidth
5832 InitDrawingSizes(-2 ,0);
5834 oldv = gameInfo.variant;
5836 DrawPosition(TRUE, boards[currentMove]);
5840 SendBoard(cps, moveNum)
5841 ChessProgramState *cps;
5844 char message[MSG_SIZ];
5846 if (cps->useSetboard) {
5847 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5848 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5849 SendToProgram(message, cps);
5855 /* Kludge to set black to move, avoiding the troublesome and now
5856 * deprecated "black" command.
5858 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5859 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5861 SendToProgram("edit\n", cps);
5862 SendToProgram("#\n", cps);
5863 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5864 bp = &boards[moveNum][i][BOARD_LEFT];
5865 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5866 if ((int) *bp < (int) BlackPawn) {
5867 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5869 if(message[0] == '+' || message[0] == '~') {
5870 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5871 PieceToChar((ChessSquare)(DEMOTED *bp)),
5874 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5875 message[1] = BOARD_RGHT - 1 - j + '1';
5876 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5878 SendToProgram(message, cps);
5883 SendToProgram("c\n", cps);
5884 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5885 bp = &boards[moveNum][i][BOARD_LEFT];
5886 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5887 if (((int) *bp != (int) EmptySquare)
5888 && ((int) *bp >= (int) BlackPawn)) {
5889 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5891 if(message[0] == '+' || message[0] == '~') {
5892 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5893 PieceToChar((ChessSquare)(DEMOTED *bp)),
5896 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5897 message[1] = BOARD_RGHT - 1 - j + '1';
5898 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5900 SendToProgram(message, cps);
5905 SendToProgram(".\n", cps);
5907 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5911 DefaultPromoChoice(int white)
5914 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5915 result = WhiteFerz; // no choice
5916 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5917 result= WhiteKing; // in Suicide Q is the last thing we want
5918 else if(gameInfo.variant == VariantSpartan)
5919 result = white ? WhiteQueen : WhiteAngel;
5920 else result = WhiteQueen;
5921 if(!white) result = WHITE_TO_BLACK result;
5925 static int autoQueen; // [HGM] oneclick
5928 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5930 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5931 /* [HGM] add Shogi promotions */
5932 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5937 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5938 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5940 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5941 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5944 piece = boards[currentMove][fromY][fromX];
5945 if(gameInfo.variant == VariantShogi) {
5946 promotionZoneSize = BOARD_HEIGHT/3;
5947 highestPromotingPiece = (int)WhiteFerz;
5948 } else if(gameInfo.variant == VariantMakruk) {
5949 promotionZoneSize = 3;
5952 // Treat Lance as Pawn when it is not representing Amazon
5953 if(gameInfo.variant != VariantSuper) {
5954 if(piece == WhiteLance) piece = WhitePawn; else
5955 if(piece == BlackLance) piece = BlackPawn;
5958 // next weed out all moves that do not touch the promotion zone at all
5959 if((int)piece >= BlackPawn) {
5960 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5962 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5964 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5965 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5968 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5970 // weed out mandatory Shogi promotions
5971 if(gameInfo.variant == VariantShogi) {
5972 if(piece >= BlackPawn) {
5973 if(toY == 0 && piece == BlackPawn ||
5974 toY == 0 && piece == BlackQueen ||
5975 toY <= 1 && piece == BlackKnight) {
5980 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5981 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5982 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5989 // weed out obviously illegal Pawn moves
5990 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5991 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5992 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5993 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5994 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5995 // note we are not allowed to test for valid (non-)capture, due to premove
5998 // we either have a choice what to promote to, or (in Shogi) whether to promote
5999 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6000 *promoChoice = PieceToChar(BlackFerz); // no choice
6003 // no sense asking what we must promote to if it is going to explode...
6004 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6005 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6008 // give caller the default choice even if we will not make it
6009 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6010 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6011 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6012 && gameInfo.variant != VariantShogi
6013 && gameInfo.variant != VariantSuper) return FALSE;
6014 if(autoQueen) return FALSE; // predetermined
6016 // suppress promotion popup on illegal moves that are not premoves
6017 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6018 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6019 if(appData.testLegality && !premove) {
6020 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6021 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6022 if(moveType != WhitePromotion && moveType != BlackPromotion)
6030 InPalace(row, column)
6032 { /* [HGM] for Xiangqi */
6033 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6034 column < (BOARD_WIDTH + 4)/2 &&
6035 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6040 PieceForSquare (x, y)
6044 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6047 return boards[currentMove][y][x];
6051 OKToStartUserMove(x, y)
6054 ChessSquare from_piece;
6057 if (matchMode) return FALSE;
6058 if (gameMode == EditPosition) return TRUE;
6060 if (x >= 0 && y >= 0)
6061 from_piece = boards[currentMove][y][x];
6063 from_piece = EmptySquare;
6065 if (from_piece == EmptySquare) return FALSE;
6067 white_piece = (int)from_piece >= (int)WhitePawn &&
6068 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6071 case PlayFromGameFile:
6073 case TwoMachinesPlay:
6081 case MachinePlaysWhite:
6082 case IcsPlayingBlack:
6083 if (appData.zippyPlay) return FALSE;
6085 DisplayMoveError(_("You are playing Black"));
6090 case MachinePlaysBlack:
6091 case IcsPlayingWhite:
6092 if (appData.zippyPlay) return FALSE;
6094 DisplayMoveError(_("You are playing White"));
6100 if (!white_piece && WhiteOnMove(currentMove)) {
6101 DisplayMoveError(_("It is White's turn"));
6104 if (white_piece && !WhiteOnMove(currentMove)) {
6105 DisplayMoveError(_("It is Black's turn"));
6108 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6109 /* Editing correspondence game history */
6110 /* Could disallow this or prompt for confirmation */
6115 case BeginningOfGame:
6116 if (appData.icsActive) return FALSE;
6117 if (!appData.noChessProgram) {
6119 DisplayMoveError(_("You are playing White"));
6126 if (!white_piece && WhiteOnMove(currentMove)) {
6127 DisplayMoveError(_("It is White's turn"));
6130 if (white_piece && !WhiteOnMove(currentMove)) {
6131 DisplayMoveError(_("It is Black's turn"));
6140 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6141 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6142 && gameMode != AnalyzeFile && gameMode != Training) {
6143 DisplayMoveError(_("Displayed position is not current"));
6150 OnlyMove(int *x, int *y, Boolean captures) {
6151 DisambiguateClosure cl;
6152 if (appData.zippyPlay) return FALSE;
6154 case MachinePlaysBlack:
6155 case IcsPlayingWhite:
6156 case BeginningOfGame:
6157 if(!WhiteOnMove(currentMove)) return FALSE;
6159 case MachinePlaysWhite:
6160 case IcsPlayingBlack:
6161 if(WhiteOnMove(currentMove)) return FALSE;
6168 cl.pieceIn = EmptySquare;
6173 cl.promoCharIn = NULLCHAR;
6174 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6175 if( cl.kind == NormalMove ||
6176 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6177 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6178 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6185 if(cl.kind != ImpossibleMove) return FALSE;
6186 cl.pieceIn = EmptySquare;
6191 cl.promoCharIn = NULLCHAR;
6192 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6193 if( cl.kind == NormalMove ||
6194 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6195 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6196 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6201 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6207 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6208 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6209 int lastLoadGameUseList = FALSE;
6210 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6211 ChessMove lastLoadGameStart = EndOfFile;
6214 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6215 int fromX, fromY, toX, toY;
6219 ChessSquare pdown, pup;
6221 /* Check if the user is playing in turn. This is complicated because we
6222 let the user "pick up" a piece before it is his turn. So the piece he
6223 tried to pick up may have been captured by the time he puts it down!
6224 Therefore we use the color the user is supposed to be playing in this
6225 test, not the color of the piece that is currently on the starting
6226 square---except in EditGame mode, where the user is playing both
6227 sides; fortunately there the capture race can't happen. (It can
6228 now happen in IcsExamining mode, but that's just too bad. The user
6229 will get a somewhat confusing message in that case.)
6233 case PlayFromGameFile:
6235 case TwoMachinesPlay:
6239 /* We switched into a game mode where moves are not accepted,
6240 perhaps while the mouse button was down. */
6243 case MachinePlaysWhite:
6244 /* User is moving for Black */
6245 if (WhiteOnMove(currentMove)) {
6246 DisplayMoveError(_("It is White's turn"));
6251 case MachinePlaysBlack:
6252 /* User is moving for White */
6253 if (!WhiteOnMove(currentMove)) {
6254 DisplayMoveError(_("It is Black's turn"));
6261 case BeginningOfGame:
6264 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6265 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6266 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6267 /* User is moving for Black */
6268 if (WhiteOnMove(currentMove)) {
6269 DisplayMoveError(_("It is White's turn"));
6273 /* User is moving for White */
6274 if (!WhiteOnMove(currentMove)) {
6275 DisplayMoveError(_("It is Black's turn"));
6281 case IcsPlayingBlack:
6282 /* User is moving for Black */
6283 if (WhiteOnMove(currentMove)) {
6284 if (!appData.premove) {
6285 DisplayMoveError(_("It is White's turn"));
6286 } else if (toX >= 0 && toY >= 0) {
6289 premoveFromX = fromX;
6290 premoveFromY = fromY;
6291 premovePromoChar = promoChar;
6293 if (appData.debugMode)
6294 fprintf(debugFP, "Got premove: fromX %d,"
6295 "fromY %d, toX %d, toY %d\n",
6296 fromX, fromY, toX, toY);
6302 case IcsPlayingWhite:
6303 /* User is moving for White */
6304 if (!WhiteOnMove(currentMove)) {
6305 if (!appData.premove) {
6306 DisplayMoveError(_("It is Black's turn"));
6307 } else if (toX >= 0 && toY >= 0) {
6310 premoveFromX = fromX;
6311 premoveFromY = fromY;
6312 premovePromoChar = promoChar;
6314 if (appData.debugMode)
6315 fprintf(debugFP, "Got premove: fromX %d,"
6316 "fromY %d, toX %d, toY %d\n",
6317 fromX, fromY, toX, toY);
6327 /* EditPosition, empty square, or different color piece;
6328 click-click move is possible */
6329 if (toX == -2 || toY == -2) {
6330 boards[0][fromY][fromX] = EmptySquare;
6331 DrawPosition(FALSE, boards[currentMove]);
6333 } else if (toX >= 0 && toY >= 0) {
6334 boards[0][toY][toX] = boards[0][fromY][fromX];
6335 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6336 if(boards[0][fromY][0] != EmptySquare) {
6337 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6338 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6341 if(fromX == BOARD_RGHT+1) {
6342 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6343 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6344 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6347 boards[0][fromY][fromX] = EmptySquare;
6348 DrawPosition(FALSE, boards[currentMove]);
6354 if(toX < 0 || toY < 0) return;
6355 pdown = boards[currentMove][fromY][fromX];
6356 pup = boards[currentMove][toY][toX];
6358 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6359 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6360 if( pup != EmptySquare ) return;
6361 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6362 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6363 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6364 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6365 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6366 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6367 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6371 /* [HGM] always test for legality, to get promotion info */
6372 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6373 fromY, fromX, toY, toX, promoChar);
6374 /* [HGM] but possibly ignore an IllegalMove result */
6375 if (appData.testLegality) {
6376 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6377 DisplayMoveError(_("Illegal move"));
6382 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6385 /* Common tail of UserMoveEvent and DropMenuEvent */
6387 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6389 int fromX, fromY, toX, toY;
6390 /*char*/int promoChar;
6394 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6395 // [HGM] superchess: suppress promotions to non-available piece
6396 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6397 if(WhiteOnMove(currentMove)) {
6398 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6400 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6404 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6405 move type in caller when we know the move is a legal promotion */
6406 if(moveType == NormalMove && promoChar)
6407 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6409 /* [HGM] <popupFix> The following if has been moved here from
6410 UserMoveEvent(). Because it seemed to belong here (why not allow
6411 piece drops in training games?), and because it can only be
6412 performed after it is known to what we promote. */
6413 if (gameMode == Training) {
6414 /* compare the move played on the board to the next move in the
6415 * game. If they match, display the move and the opponent's response.
6416 * If they don't match, display an error message.
6420 CopyBoard(testBoard, boards[currentMove]);
6421 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6423 if (CompareBoards(testBoard, boards[currentMove+1])) {
6424 ForwardInner(currentMove+1);
6426 /* Autoplay the opponent's response.
6427 * if appData.animate was TRUE when Training mode was entered,
6428 * the response will be animated.
6430 saveAnimate = appData.animate;
6431 appData.animate = animateTraining;
6432 ForwardInner(currentMove+1);
6433 appData.animate = saveAnimate;
6435 /* check for the end of the game */
6436 if (currentMove >= forwardMostMove) {
6437 gameMode = PlayFromGameFile;
6439 SetTrainingModeOff();
6440 DisplayInformation(_("End of game"));
6443 DisplayError(_("Incorrect move"), 0);
6448 /* Ok, now we know that the move is good, so we can kill
6449 the previous line in Analysis Mode */
6450 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6451 && currentMove < forwardMostMove) {
6452 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6453 else forwardMostMove = currentMove;
6456 /* If we need the chess program but it's dead, restart it */
6457 ResurrectChessProgram();
6459 /* A user move restarts a paused game*/
6463 thinkOutput[0] = NULLCHAR;
6465 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6467 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6468 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6472 if (gameMode == BeginningOfGame) {
6473 if (appData.noChessProgram) {
6474 gameMode = EditGame;
6478 gameMode = MachinePlaysBlack;
6481 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6483 if (first.sendName) {
6484 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6485 SendToProgram(buf, &first);
6492 /* Relay move to ICS or chess engine */
6493 if (appData.icsActive) {
6494 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6495 gameMode == IcsExamining) {
6496 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6497 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6499 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6501 // also send plain move, in case ICS does not understand atomic claims
6502 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6506 if (first.sendTime && (gameMode == BeginningOfGame ||
6507 gameMode == MachinePlaysWhite ||
6508 gameMode == MachinePlaysBlack)) {
6509 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6511 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6512 // [HGM] book: if program might be playing, let it use book
6513 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6514 first.maybeThinking = TRUE;
6515 } else SendMoveToProgram(forwardMostMove-1, &first);
6516 if (currentMove == cmailOldMove + 1) {
6517 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6521 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6525 if(appData.testLegality)
6526 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6532 if (WhiteOnMove(currentMove)) {
6533 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6535 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6539 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6544 case MachinePlaysBlack:
6545 case MachinePlaysWhite:
6546 /* disable certain menu options while machine is thinking */
6547 SetMachineThinkingEnables();
6554 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6555 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6557 if(bookHit) { // [HGM] book: simulate book reply
6558 static char bookMove[MSG_SIZ]; // a bit generous?
6560 programStats.nodes = programStats.depth = programStats.time =
6561 programStats.score = programStats.got_only_move = 0;
6562 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6564 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6565 strcat(bookMove, bookHit);
6566 HandleMachineMove(bookMove, &first);
6572 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6579 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6580 Markers *m = (Markers *) closure;
6581 if(rf == fromY && ff == fromX)
6582 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6583 || kind == WhiteCapturesEnPassant
6584 || kind == BlackCapturesEnPassant);
6585 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6589 MarkTargetSquares(int clear)
6592 if(!appData.markers || !appData.highlightDragging ||
6593 !appData.testLegality || gameMode == EditPosition) return;
6595 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6598 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6599 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6600 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6602 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6605 DrawPosition(TRUE, NULL);
6609 Explode(Board board, int fromX, int fromY, int toX, int toY)
6611 if(gameInfo.variant == VariantAtomic &&
6612 (board[toY][toX] != EmptySquare || // capture?
6613 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6614 board[fromY][fromX] == BlackPawn )
6616 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6622 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6624 int CanPromote(ChessSquare piece, int y)
6626 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6627 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6628 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6629 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6630 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6631 gameInfo.variant == VariantMakruk) return FALSE;
6632 return (piece == BlackPawn && y == 1 ||
6633 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6634 piece == BlackLance && y == 1 ||
6635 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6638 void LeftClick(ClickType clickType, int xPix, int yPix)
6641 Boolean saveAnimate;
6642 static int second = 0, promotionChoice = 0, clearFlag = 0;
6643 char promoChoice = NULLCHAR;
6646 if(appData.seekGraph && appData.icsActive && loggedOn &&
6647 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6648 SeekGraphClick(clickType, xPix, yPix, 0);
6652 if (clickType == Press) ErrorPopDown();
6653 MarkTargetSquares(1);
6655 x = EventToSquare(xPix, BOARD_WIDTH);
6656 y = EventToSquare(yPix, BOARD_HEIGHT);
6657 if (!flipView && y >= 0) {
6658 y = BOARD_HEIGHT - 1 - y;
6660 if (flipView && x >= 0) {
6661 x = BOARD_WIDTH - 1 - x;
6664 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6665 defaultPromoChoice = promoSweep;
6666 promoSweep = EmptySquare; // terminate sweep
6667 promoDefaultAltered = TRUE;
6668 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6671 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6672 if(clickType == Release) return; // ignore upclick of click-click destination
6673 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6674 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6675 if(gameInfo.holdingsWidth &&
6676 (WhiteOnMove(currentMove)
6677 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6678 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6679 // click in right holdings, for determining promotion piece
6680 ChessSquare p = boards[currentMove][y][x];
6681 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6682 if(p != EmptySquare) {
6683 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6688 DrawPosition(FALSE, boards[currentMove]);
6692 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6693 if(clickType == Press
6694 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6695 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6696 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6699 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6700 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6702 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6703 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6704 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6705 defaultPromoChoice = DefaultPromoChoice(side);
6708 autoQueen = appData.alwaysPromoteToQueen;
6712 gatingPiece = EmptySquare;
6713 if (clickType != Press) {
6714 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6715 DragPieceEnd(xPix, yPix); dragging = 0;
6716 DrawPosition(FALSE, NULL);
6720 fromX = x; fromY = y;
6721 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6722 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6723 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6725 if (OKToStartUserMove(fromX, fromY)) {
6727 MarkTargetSquares(0);
6728 DragPieceBegin(xPix, yPix); dragging = 1;
6729 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6730 promoSweep = defaultPromoChoice;
6731 selectFlag = 0; lastX = xPix; lastY = yPix;
6732 Sweep(0); // Pawn that is going to promote: preview promotion piece
6733 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6735 if (appData.highlightDragging) {
6736 SetHighlights(fromX, fromY, -1, -1);
6738 } else fromX = fromY = -1;
6744 if (clickType == Press && gameMode != EditPosition) {
6749 // ignore off-board to clicks
6750 if(y < 0 || x < 0) return;
6752 /* Check if clicking again on the same color piece */
6753 fromP = boards[currentMove][fromY][fromX];
6754 toP = boards[currentMove][y][x];
6755 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6756 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6757 WhitePawn <= toP && toP <= WhiteKing &&
6758 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6759 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6760 (BlackPawn <= fromP && fromP <= BlackKing &&
6761 BlackPawn <= toP && toP <= BlackKing &&
6762 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6763 !(fromP == BlackKing && toP == BlackRook && frc))) {
6764 /* Clicked again on same color piece -- changed his mind */
6765 second = (x == fromX && y == fromY);
6766 promoDefaultAltered = FALSE;
6767 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6768 if (appData.highlightDragging) {
6769 SetHighlights(x, y, -1, -1);
6773 if (OKToStartUserMove(x, y)) {
6774 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6775 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6776 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6777 gatingPiece = boards[currentMove][fromY][fromX];
6778 else gatingPiece = EmptySquare;
6780 fromY = y; dragging = 1;
6781 MarkTargetSquares(0);
6782 DragPieceBegin(xPix, yPix);
6783 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6784 promoSweep = defaultPromoChoice;
6785 selectFlag = 0; lastX = xPix; lastY = yPix;
6786 Sweep(0); // Pawn that is going to promote: preview promotion piece
6790 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6793 // ignore clicks on holdings
6794 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6797 if (clickType == Release && x == fromX && y == fromY) {
6798 DragPieceEnd(xPix, yPix); dragging = 0;
6800 // a deferred attempt to click-click move an empty square on top of a piece
6801 boards[currentMove][y][x] = EmptySquare;
6803 DrawPosition(FALSE, boards[currentMove]);
6804 fromX = fromY = -1; clearFlag = 0;
6807 if (appData.animateDragging) {
6808 /* Undo animation damage if any */
6809 DrawPosition(FALSE, NULL);
6812 /* Second up/down in same square; just abort move */
6815 gatingPiece = EmptySquare;
6818 ClearPremoveHighlights();
6820 /* First upclick in same square; start click-click mode */
6821 SetHighlights(x, y, -1, -1);
6828 /* we now have a different from- and (possibly off-board) to-square */
6829 /* Completed move */
6832 saveAnimate = appData.animate;
6833 if (clickType == Press) {
6834 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6835 // must be Edit Position mode with empty-square selected
6836 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6837 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6840 /* Finish clickclick move */
6841 if (appData.animate || appData.highlightLastMove) {
6842 SetHighlights(fromX, fromY, toX, toY);
6847 /* Finish drag move */
6848 if (appData.highlightLastMove) {
6849 SetHighlights(fromX, fromY, toX, toY);
6853 DragPieceEnd(xPix, yPix); dragging = 0;
6854 /* Don't animate move and drag both */
6855 appData.animate = FALSE;
6858 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6859 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6860 ChessSquare piece = boards[currentMove][fromY][fromX];
6861 if(gameMode == EditPosition && piece != EmptySquare &&
6862 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6865 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6866 n = PieceToNumber(piece - (int)BlackPawn);
6867 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6868 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6869 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6871 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6872 n = PieceToNumber(piece);
6873 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6874 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6875 boards[currentMove][n][BOARD_WIDTH-2]++;
6877 boards[currentMove][fromY][fromX] = EmptySquare;
6881 DrawPosition(TRUE, boards[currentMove]);
6885 // off-board moves should not be highlighted
6886 if(x < 0 || y < 0) ClearHighlights();
6888 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6890 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6891 SetHighlights(fromX, fromY, toX, toY);
6892 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6893 // [HGM] super: promotion to captured piece selected from holdings
6894 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6895 promotionChoice = TRUE;
6896 // kludge follows to temporarily execute move on display, without promoting yet
6897 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6898 boards[currentMove][toY][toX] = p;
6899 DrawPosition(FALSE, boards[currentMove]);
6900 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6901 boards[currentMove][toY][toX] = q;
6902 DisplayMessage("Click in holdings to choose piece", "");
6907 int oldMove = currentMove;
6908 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6909 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6910 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6911 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6912 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6913 DrawPosition(TRUE, boards[currentMove]);
6916 appData.animate = saveAnimate;
6917 if (appData.animate || appData.animateDragging) {
6918 /* Undo animation damage if needed */
6919 DrawPosition(FALSE, NULL);
6923 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6924 { // front-end-free part taken out of PieceMenuPopup
6925 int whichMenu; int xSqr, ySqr;
6927 if(seekGraphUp) { // [HGM] seekgraph
6928 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6929 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6933 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6934 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6935 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6936 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6937 if(action == Press) {
6938 originalFlip = flipView;
6939 flipView = !flipView; // temporarily flip board to see game from partners perspective
6940 DrawPosition(TRUE, partnerBoard);
6941 DisplayMessage(partnerStatus, "");
6943 } else if(action == Release) {
6944 flipView = originalFlip;
6945 DrawPosition(TRUE, boards[currentMove]);
6951 xSqr = EventToSquare(x, BOARD_WIDTH);
6952 ySqr = EventToSquare(y, BOARD_HEIGHT);
6953 if (action == Release) {
6954 if(pieceSweep != EmptySquare) {
6955 EditPositionMenuEvent(pieceSweep, toX, toY);
6956 pieceSweep = EmptySquare;
6957 } else UnLoadPV(); // [HGM] pv
6959 if (action != Press) return -2; // return code to be ignored
6962 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6964 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6965 if (xSqr < 0 || ySqr < 0) return -1;
6966 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6967 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6968 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6969 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6973 if(!appData.icsEngineAnalyze) return -1;
6974 case IcsPlayingWhite:
6975 case IcsPlayingBlack:
6976 if(!appData.zippyPlay) goto noZip;
6979 case MachinePlaysWhite:
6980 case MachinePlaysBlack:
6981 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6982 if (!appData.dropMenu) {
6984 return 2; // flag front-end to grab mouse events
6986 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6987 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6990 if (xSqr < 0 || ySqr < 0) return -1;
6991 if (!appData.dropMenu || appData.testLegality &&
6992 gameInfo.variant != VariantBughouse &&
6993 gameInfo.variant != VariantCrazyhouse) return -1;
6994 whichMenu = 1; // drop menu
7000 if (((*fromX = xSqr) < 0) ||
7001 ((*fromY = ySqr) < 0)) {
7002 *fromX = *fromY = -1;
7006 *fromX = BOARD_WIDTH - 1 - *fromX;
7008 *fromY = BOARD_HEIGHT - 1 - *fromY;
7013 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7015 // char * hint = lastHint;
7016 FrontEndProgramStats stats;
7018 stats.which = cps == &first ? 0 : 1;
7019 stats.depth = cpstats->depth;
7020 stats.nodes = cpstats->nodes;
7021 stats.score = cpstats->score;
7022 stats.time = cpstats->time;
7023 stats.pv = cpstats->movelist;
7024 stats.hint = lastHint;
7025 stats.an_move_index = 0;
7026 stats.an_move_count = 0;
7028 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7029 stats.hint = cpstats->move_name;
7030 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7031 stats.an_move_count = cpstats->nr_moves;
7034 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7036 SetProgramStats( &stats );
7040 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7041 { // count all piece types
7043 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7044 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7045 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7048 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7049 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7050 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7051 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7052 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7053 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7058 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7060 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7061 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7063 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7064 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7065 if(myPawns == 2 && nMine == 3) // KPP
7066 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7067 if(myPawns == 1 && nMine == 2) // KP
7068 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7069 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7070 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7071 if(myPawns) return FALSE;
7072 if(pCnt[WhiteRook+side])
7073 return pCnt[BlackRook-side] ||
7074 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7075 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7076 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7077 if(pCnt[WhiteCannon+side]) {
7078 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7079 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7081 if(pCnt[WhiteKnight+side])
7082 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7087 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7089 VariantClass v = gameInfo.variant;
7091 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7092 if(v == VariantShatranj) return TRUE; // always winnable through baring
7093 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7094 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7096 if(v == VariantXiangqi) {
7097 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7099 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7100 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7101 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7102 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7103 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7104 if(stale) // we have at least one last-rank P plus perhaps C
7105 return majors // KPKX
7106 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7108 return pCnt[WhiteFerz+side] // KCAK
7109 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7110 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7111 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7113 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7114 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7116 if(nMine == 1) return FALSE; // bare King
7117 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7118 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7119 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7120 // by now we have King + 1 piece (or multiple Bishops on the same color)
7121 if(pCnt[WhiteKnight+side])
7122 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7123 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7124 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7126 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7127 if(pCnt[WhiteAlfil+side])
7128 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7129 if(pCnt[WhiteWazir+side])
7130 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7137 Adjudicate(ChessProgramState *cps)
7138 { // [HGM] some adjudications useful with buggy engines
7139 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7140 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7141 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7142 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7143 int k, count = 0; static int bare = 1;
7144 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7145 Boolean canAdjudicate = !appData.icsActive;
7147 // most tests only when we understand the game, i.e. legality-checking on
7148 if( appData.testLegality )
7149 { /* [HGM] Some more adjudications for obstinate engines */
7150 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7151 static int moveCount = 6;
7153 char *reason = NULL;
7155 /* Count what is on board. */
7156 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7158 /* Some material-based adjudications that have to be made before stalemate test */
7159 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7160 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7161 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7162 if(canAdjudicate && appData.checkMates) {
7164 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7165 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7166 "Xboard adjudication: King destroyed", GE_XBOARD );
7171 /* Bare King in Shatranj (loses) or Losers (wins) */
7172 if( nrW == 1 || nrB == 1) {
7173 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7174 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7175 if(canAdjudicate && appData.checkMates) {
7177 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7178 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7179 "Xboard adjudication: Bare king", GE_XBOARD );
7183 if( gameInfo.variant == VariantShatranj && --bare < 0)
7185 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7186 if(canAdjudicate && appData.checkMates) {
7187 /* but only adjudicate if adjudication enabled */
7189 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7190 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7191 "Xboard adjudication: Bare king", GE_XBOARD );
7198 // don't wait for engine to announce game end if we can judge ourselves
7199 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7201 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7202 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7203 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7204 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7207 reason = "Xboard adjudication: 3rd check";
7208 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7218 reason = "Xboard adjudication: Stalemate";
7219 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7220 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7221 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7222 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7223 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7224 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7225 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7226 EP_CHECKMATE : EP_WINS);
7227 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7228 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7232 reason = "Xboard adjudication: Checkmate";
7233 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7237 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7239 result = GameIsDrawn; break;
7241 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7243 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7247 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7249 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7250 GameEnds( result, reason, GE_XBOARD );
7254 /* Next absolutely insufficient mating material. */
7255 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7256 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7257 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7259 /* always flag draws, for judging claims */
7260 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7262 if(canAdjudicate && appData.materialDraws) {
7263 /* but only adjudicate them if adjudication enabled */
7264 if(engineOpponent) {
7265 SendToProgram("force\n", engineOpponent); // suppress reply
7266 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7268 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7273 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7274 if(gameInfo.variant == VariantXiangqi ?
7275 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7277 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7278 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7279 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7280 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7282 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7283 { /* if the first 3 moves do not show a tactical win, declare draw */
7284 if(engineOpponent) {
7285 SendToProgram("force\n", engineOpponent); // suppress reply
7286 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7288 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7291 } else moveCount = 6;
7293 if (appData.debugMode) { int i;
7294 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7295 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7296 appData.drawRepeats);
7297 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7298 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7302 // Repetition draws and 50-move rule can be applied independently of legality testing
7304 /* Check for rep-draws */
7306 for(k = forwardMostMove-2;
7307 k>=backwardMostMove && k>=forwardMostMove-100 &&
7308 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7309 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7312 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7313 /* compare castling rights */
7314 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7315 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7316 rights++; /* King lost rights, while rook still had them */
7317 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7318 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7319 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7320 rights++; /* but at least one rook lost them */
7322 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7323 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7325 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7326 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7327 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7330 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7331 && appData.drawRepeats > 1) {
7332 /* adjudicate after user-specified nr of repeats */
7333 int result = GameIsDrawn;
7334 char *details = "XBoard adjudication: repetition draw";
7335 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7336 // [HGM] xiangqi: check for forbidden perpetuals
7337 int m, ourPerpetual = 1, hisPerpetual = 1;
7338 for(m=forwardMostMove; m>k; m-=2) {
7339 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7340 ourPerpetual = 0; // the current mover did not always check
7341 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7342 hisPerpetual = 0; // the opponent did not always check
7344 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7345 ourPerpetual, hisPerpetual);
7346 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7347 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7348 details = "Xboard adjudication: perpetual checking";
7350 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7351 break; // (or we would have caught him before). Abort repetition-checking loop.
7353 // Now check for perpetual chases
7354 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7355 hisPerpetual = PerpetualChase(k, forwardMostMove);
7356 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7357 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7358 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7359 details = "Xboard adjudication: perpetual chasing";
7361 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7362 break; // Abort repetition-checking loop.
7364 // if neither of us is checking or chasing all the time, or both are, it is draw
7366 if(engineOpponent) {
7367 SendToProgram("force\n", engineOpponent); // suppress reply
7368 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7370 GameEnds( result, details, GE_XBOARD );
7373 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7374 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7378 /* Now we test for 50-move draws. Determine ply count */
7379 count = forwardMostMove;
7380 /* look for last irreversble move */
7381 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7383 /* if we hit starting position, add initial plies */
7384 if( count == backwardMostMove )
7385 count -= initialRulePlies;
7386 count = forwardMostMove - count;
7387 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7388 // adjust reversible move counter for checks in Xiangqi
7389 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7390 if(i < backwardMostMove) i = backwardMostMove;
7391 while(i <= forwardMostMove) {
7392 lastCheck = inCheck; // check evasion does not count
7393 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7394 if(inCheck || lastCheck) count--; // check does not count
7399 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7400 /* this is used to judge if draw claims are legal */
7401 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7402 if(engineOpponent) {
7403 SendToProgram("force\n", engineOpponent); // suppress reply
7404 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7406 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7410 /* if draw offer is pending, treat it as a draw claim
7411 * when draw condition present, to allow engines a way to
7412 * claim draws before making their move to avoid a race
7413 * condition occurring after their move
7415 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7417 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7418 p = "Draw claim: 50-move rule";
7419 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7420 p = "Draw claim: 3-fold repetition";
7421 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7422 p = "Draw claim: insufficient mating material";
7423 if( p != NULL && canAdjudicate) {
7424 if(engineOpponent) {
7425 SendToProgram("force\n", engineOpponent); // suppress reply
7426 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7428 GameEnds( GameIsDrawn, p, GE_XBOARD );
7433 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7434 if(engineOpponent) {
7435 SendToProgram("force\n", engineOpponent); // suppress reply
7436 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7438 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7444 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7445 { // [HGM] book: this routine intercepts moves to simulate book replies
7446 char *bookHit = NULL;
7448 //first determine if the incoming move brings opponent into his book
7449 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7450 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7451 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7452 if(bookHit != NULL && !cps->bookSuspend) {
7453 // make sure opponent is not going to reply after receiving move to book position
7454 SendToProgram("force\n", cps);
7455 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7457 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7458 // now arrange restart after book miss
7460 // after a book hit we never send 'go', and the code after the call to this routine
7461 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7463 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7464 SendToProgram(buf, cps);
7465 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7466 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7467 SendToProgram("go\n", cps);
7468 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7469 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7470 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7471 SendToProgram("go\n", cps);
7472 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7474 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7478 ChessProgramState *savedState;
7479 void DeferredBookMove(void)
7481 if(savedState->lastPing != savedState->lastPong)
7482 ScheduleDelayedEvent(DeferredBookMove, 10);
7484 HandleMachineMove(savedMessage, savedState);
7488 HandleMachineMove(message, cps)
7490 ChessProgramState *cps;
7492 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7493 char realname[MSG_SIZ];
7494 int fromX, fromY, toX, toY;
7503 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7505 * Kludge to ignore BEL characters
7507 while (*message == '\007') message++;
7510 * [HGM] engine debug message: ignore lines starting with '#' character
7512 if(cps->debug && *message == '#') return;
7515 * Look for book output
7517 if (cps == &first && bookRequested) {
7518 if (message[0] == '\t' || message[0] == ' ') {
7519 /* Part of the book output is here; append it */
7520 strcat(bookOutput, message);
7521 strcat(bookOutput, " \n");
7523 } else if (bookOutput[0] != NULLCHAR) {
7524 /* All of book output has arrived; display it */
7525 char *p = bookOutput;
7526 while (*p != NULLCHAR) {
7527 if (*p == '\t') *p = ' ';
7530 DisplayInformation(bookOutput);
7531 bookRequested = FALSE;
7532 /* Fall through to parse the current output */
7537 * Look for machine move.
7539 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7540 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7542 /* This method is only useful on engines that support ping */
7543 if (cps->lastPing != cps->lastPong) {
7544 if (gameMode == BeginningOfGame) {
7545 /* Extra move from before last new; ignore */
7546 if (appData.debugMode) {
7547 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7550 if (appData.debugMode) {
7551 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7552 cps->which, gameMode);
7555 SendToProgram("undo\n", cps);
7561 case BeginningOfGame:
7562 /* Extra move from before last reset; ignore */
7563 if (appData.debugMode) {
7564 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7571 /* Extra move after we tried to stop. The mode test is
7572 not a reliable way of detecting this problem, but it's
7573 the best we can do on engines that don't support ping.
7575 if (appData.debugMode) {
7576 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7577 cps->which, gameMode);
7579 SendToProgram("undo\n", cps);
7582 case MachinePlaysWhite:
7583 case IcsPlayingWhite:
7584 machineWhite = TRUE;
7587 case MachinePlaysBlack:
7588 case IcsPlayingBlack:
7589 machineWhite = FALSE;
7592 case TwoMachinesPlay:
7593 machineWhite = (cps->twoMachinesColor[0] == 'w');
7596 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7597 if (appData.debugMode) {
7599 "Ignoring move out of turn by %s, gameMode %d"
7600 ", forwardMost %d\n",
7601 cps->which, gameMode, forwardMostMove);
7606 if (appData.debugMode) { int f = forwardMostMove;
7607 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7608 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7609 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7611 if(cps->alphaRank) AlphaRank(machineMove, 4);
7612 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7613 &fromX, &fromY, &toX, &toY, &promoChar)) {
7614 /* Machine move could not be parsed; ignore it. */
7615 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7616 machineMove, _(cps->which));
7617 DisplayError(buf1, 0);
7618 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7619 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7620 if (gameMode == TwoMachinesPlay) {
7621 GameEnds(machineWhite ? BlackWins : WhiteWins,
7627 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7628 /* So we have to redo legality test with true e.p. status here, */
7629 /* to make sure an illegal e.p. capture does not slip through, */
7630 /* to cause a forfeit on a justified illegal-move complaint */
7631 /* of the opponent. */
7632 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7634 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7635 fromY, fromX, toY, toX, promoChar);
7636 if (appData.debugMode) {
7638 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7639 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7640 fprintf(debugFP, "castling rights\n");
7642 if(moveType == IllegalMove) {
7643 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7644 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7645 GameEnds(machineWhite ? BlackWins : WhiteWins,
7648 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7649 /* [HGM] Kludge to handle engines that send FRC-style castling
7650 when they shouldn't (like TSCP-Gothic) */
7652 case WhiteASideCastleFR:
7653 case BlackASideCastleFR:
7655 currentMoveString[2]++;
7657 case WhiteHSideCastleFR:
7658 case BlackHSideCastleFR:
7660 currentMoveString[2]--;
7662 default: ; // nothing to do, but suppresses warning of pedantic compilers
7665 hintRequested = FALSE;
7666 lastHint[0] = NULLCHAR;
7667 bookRequested = FALSE;
7668 /* Program may be pondering now */
7669 cps->maybeThinking = TRUE;
7670 if (cps->sendTime == 2) cps->sendTime = 1;
7671 if (cps->offeredDraw) cps->offeredDraw--;
7673 /* [AS] Save move info*/
7674 pvInfoList[ forwardMostMove ].score = programStats.score;
7675 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7676 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7678 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7680 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7681 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7684 while( count < adjudicateLossPlies ) {
7685 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7688 score = -score; /* Flip score for winning side */
7691 if( score > adjudicateLossThreshold ) {
7698 if( count >= adjudicateLossPlies ) {
7699 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7701 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7702 "Xboard adjudication",
7709 if(Adjudicate(cps)) {
7710 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7711 return; // [HGM] adjudicate: for all automatic game ends
7715 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7717 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7718 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7720 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7722 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7724 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7725 char buf[3*MSG_SIZ];
7727 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7728 programStats.score / 100.,
7730 programStats.time / 100.,
7731 (unsigned int)programStats.nodes,
7732 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7733 programStats.movelist);
7735 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7740 /* [AS] Clear stats for next move */
7741 ClearProgramStats();
7742 thinkOutput[0] = NULLCHAR;
7743 hiddenThinkOutputState = 0;
7746 if (gameMode == TwoMachinesPlay) {
7747 /* [HGM] relaying draw offers moved to after reception of move */
7748 /* and interpreting offer as claim if it brings draw condition */
7749 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7750 SendToProgram("draw\n", cps->other);
7752 if (cps->other->sendTime) {
7753 SendTimeRemaining(cps->other,
7754 cps->other->twoMachinesColor[0] == 'w');
7756 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7757 if (firstMove && !bookHit) {
7759 if (cps->other->useColors) {
7760 SendToProgram(cps->other->twoMachinesColor, cps->other);
7762 SendToProgram("go\n", cps->other);
7764 cps->other->maybeThinking = TRUE;
7767 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7769 if (!pausing && appData.ringBellAfterMoves) {
7774 * Reenable menu items that were disabled while
7775 * machine was thinking
7777 if (gameMode != TwoMachinesPlay)
7778 SetUserThinkingEnables();
7780 // [HGM] book: after book hit opponent has received move and is now in force mode
7781 // force the book reply into it, and then fake that it outputted this move by jumping
7782 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7784 static char bookMove[MSG_SIZ]; // a bit generous?
7786 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7787 strcat(bookMove, bookHit);
7790 programStats.nodes = programStats.depth = programStats.time =
7791 programStats.score = programStats.got_only_move = 0;
7792 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7794 if(cps->lastPing != cps->lastPong) {
7795 savedMessage = message; // args for deferred call
7797 ScheduleDelayedEvent(DeferredBookMove, 10);
7806 /* Set special modes for chess engines. Later something general
7807 * could be added here; for now there is just one kludge feature,
7808 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7809 * when "xboard" is given as an interactive command.
7811 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7812 cps->useSigint = FALSE;
7813 cps->useSigterm = FALSE;
7815 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7816 ParseFeatures(message+8, cps);
7817 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7820 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7821 int dummy, s=6; char buf[MSG_SIZ];
7822 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7823 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7824 ParseFEN(boards[0], &dummy, message+s);
7825 DrawPosition(TRUE, boards[0]);
7826 startedFromSetupPosition = TRUE;
7829 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7830 * want this, I was asked to put it in, and obliged.
7832 if (!strncmp(message, "setboard ", 9)) {
7833 Board initial_position;
7835 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7837 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7838 DisplayError(_("Bad FEN received from engine"), 0);
7842 CopyBoard(boards[0], initial_position);
7843 initialRulePlies = FENrulePlies;
7844 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7845 else gameMode = MachinePlaysBlack;
7846 DrawPosition(FALSE, boards[currentMove]);
7852 * Look for communication commands
7854 if (!strncmp(message, "telluser ", 9)) {
7855 if(message[9] == '\\' && message[10] == '\\')
7856 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7857 DisplayNote(message + 9);
7860 if (!strncmp(message, "tellusererror ", 14)) {
7862 if(message[14] == '\\' && message[15] == '\\')
7863 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7864 DisplayError(message + 14, 0);
7867 if (!strncmp(message, "tellopponent ", 13)) {
7868 if (appData.icsActive) {
7870 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7874 DisplayNote(message + 13);
7878 if (!strncmp(message, "tellothers ", 11)) {
7879 if (appData.icsActive) {
7881 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7887 if (!strncmp(message, "tellall ", 8)) {
7888 if (appData.icsActive) {
7890 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7894 DisplayNote(message + 8);
7898 if (strncmp(message, "warning", 7) == 0) {
7899 /* Undocumented feature, use tellusererror in new code */
7900 DisplayError(message, 0);
7903 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7904 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7905 strcat(realname, " query");
7906 AskQuestion(realname, buf2, buf1, cps->pr);
7909 /* Commands from the engine directly to ICS. We don't allow these to be
7910 * sent until we are logged on. Crafty kibitzes have been known to
7911 * interfere with the login process.
7914 if (!strncmp(message, "tellics ", 8)) {
7915 SendToICS(message + 8);
7919 if (!strncmp(message, "tellicsnoalias ", 15)) {
7920 SendToICS(ics_prefix);
7921 SendToICS(message + 15);
7925 /* The following are for backward compatibility only */
7926 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7927 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7928 SendToICS(ics_prefix);
7934 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7938 * If the move is illegal, cancel it and redraw the board.
7939 * Also deal with other error cases. Matching is rather loose
7940 * here to accommodate engines written before the spec.
7942 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7943 strncmp(message, "Error", 5) == 0) {
7944 if (StrStr(message, "name") ||
7945 StrStr(message, "rating") || StrStr(message, "?") ||
7946 StrStr(message, "result") || StrStr(message, "board") ||
7947 StrStr(message, "bk") || StrStr(message, "computer") ||
7948 StrStr(message, "variant") || StrStr(message, "hint") ||
7949 StrStr(message, "random") || StrStr(message, "depth") ||
7950 StrStr(message, "accepted")) {
7953 if (StrStr(message, "protover")) {
7954 /* Program is responding to input, so it's apparently done
7955 initializing, and this error message indicates it is
7956 protocol version 1. So we don't need to wait any longer
7957 for it to initialize and send feature commands. */
7958 FeatureDone(cps, 1);
7959 cps->protocolVersion = 1;
7962 cps->maybeThinking = FALSE;
7964 if (StrStr(message, "draw")) {
7965 /* Program doesn't have "draw" command */
7966 cps->sendDrawOffers = 0;
7969 if (cps->sendTime != 1 &&
7970 (StrStr(message, "time") || StrStr(message, "otim"))) {
7971 /* Program apparently doesn't have "time" or "otim" command */
7975 if (StrStr(message, "analyze")) {
7976 cps->analysisSupport = FALSE;
7977 cps->analyzing = FALSE;
7979 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7980 DisplayError(buf2, 0);
7983 if (StrStr(message, "(no matching move)st")) {
7984 /* Special kludge for GNU Chess 4 only */
7985 cps->stKludge = TRUE;
7986 SendTimeControl(cps, movesPerSession, timeControl,
7987 timeIncrement, appData.searchDepth,
7991 if (StrStr(message, "(no matching move)sd")) {
7992 /* Special kludge for GNU Chess 4 only */
7993 cps->sdKludge = TRUE;
7994 SendTimeControl(cps, movesPerSession, timeControl,
7995 timeIncrement, appData.searchDepth,
7999 if (!StrStr(message, "llegal")) {
8002 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8003 gameMode == IcsIdle) return;
8004 if (forwardMostMove <= backwardMostMove) return;
8005 if (pausing) PauseEvent();
8006 if(appData.forceIllegal) {
8007 // [HGM] illegal: machine refused move; force position after move into it
8008 SendToProgram("force\n", cps);
8009 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8010 // we have a real problem now, as SendBoard will use the a2a3 kludge
8011 // when black is to move, while there might be nothing on a2 or black
8012 // might already have the move. So send the board as if white has the move.
8013 // But first we must change the stm of the engine, as it refused the last move
8014 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8015 if(WhiteOnMove(forwardMostMove)) {
8016 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8017 SendBoard(cps, forwardMostMove); // kludgeless board
8019 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8020 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8021 SendBoard(cps, forwardMostMove+1); // kludgeless board
8023 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8024 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8025 gameMode == TwoMachinesPlay)
8026 SendToProgram("go\n", cps);
8029 if (gameMode == PlayFromGameFile) {
8030 /* Stop reading this game file */
8031 gameMode = EditGame;
8034 /* [HGM] illegal-move claim should forfeit game when Xboard */
8035 /* only passes fully legal moves */
8036 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8037 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8038 "False illegal-move claim", GE_XBOARD );
8039 return; // do not take back move we tested as valid
8041 currentMove = forwardMostMove-1;
8042 DisplayMove(currentMove-1); /* before DisplayMoveError */
8043 SwitchClocks(forwardMostMove-1); // [HGM] race
8044 DisplayBothClocks();
8045 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8046 parseList[currentMove], _(cps->which));
8047 DisplayMoveError(buf1);
8048 DrawPosition(FALSE, boards[currentMove]);
8051 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8052 /* Program has a broken "time" command that
8053 outputs a string not ending in newline.
8059 * If chess program startup fails, exit with an error message.
8060 * Attempts to recover here are futile.
8062 if ((StrStr(message, "unknown host") != NULL)
8063 || (StrStr(message, "No remote directory") != NULL)
8064 || (StrStr(message, "not found") != NULL)
8065 || (StrStr(message, "No such file") != NULL)
8066 || (StrStr(message, "can't alloc") != NULL)
8067 || (StrStr(message, "Permission denied") != NULL)) {
8069 cps->maybeThinking = FALSE;
8070 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8071 _(cps->which), cps->program, cps->host, message);
8072 RemoveInputSource(cps->isr);
8073 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8074 if(cps == &first) appData.noChessProgram = TRUE;
8075 DisplayError(buf1, 0);
8081 * Look for hint output
8083 if (sscanf(message, "Hint: %s", buf1) == 1) {
8084 if (cps == &first && hintRequested) {
8085 hintRequested = FALSE;
8086 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8087 &fromX, &fromY, &toX, &toY, &promoChar)) {
8088 (void) CoordsToAlgebraic(boards[forwardMostMove],
8089 PosFlags(forwardMostMove),
8090 fromY, fromX, toY, toX, promoChar, buf1);
8091 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8092 DisplayInformation(buf2);
8094 /* Hint move could not be parsed!? */
8095 snprintf(buf2, sizeof(buf2),
8096 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8097 buf1, _(cps->which));
8098 DisplayError(buf2, 0);
8101 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8107 * Ignore other messages if game is not in progress
8109 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8110 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8113 * look for win, lose, draw, or draw offer
8115 if (strncmp(message, "1-0", 3) == 0) {
8116 char *p, *q, *r = "";
8117 p = strchr(message, '{');
8125 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8127 } else if (strncmp(message, "0-1", 3) == 0) {
8128 char *p, *q, *r = "";
8129 p = strchr(message, '{');
8137 /* Kludge for Arasan 4.1 bug */
8138 if (strcmp(r, "Black resigns") == 0) {
8139 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8142 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8144 } else if (strncmp(message, "1/2", 3) == 0) {
8145 char *p, *q, *r = "";
8146 p = strchr(message, '{');
8155 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8158 } else if (strncmp(message, "White resign", 12) == 0) {
8159 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8161 } else if (strncmp(message, "Black resign", 12) == 0) {
8162 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8164 } else if (strncmp(message, "White matches", 13) == 0 ||
8165 strncmp(message, "Black matches", 13) == 0 ) {
8166 /* [HGM] ignore GNUShogi noises */
8168 } else if (strncmp(message, "White", 5) == 0 &&
8169 message[5] != '(' &&
8170 StrStr(message, "Black") == NULL) {
8171 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8173 } else if (strncmp(message, "Black", 5) == 0 &&
8174 message[5] != '(') {
8175 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8177 } else if (strcmp(message, "resign") == 0 ||
8178 strcmp(message, "computer resigns") == 0) {
8180 case MachinePlaysBlack:
8181 case IcsPlayingBlack:
8182 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8184 case MachinePlaysWhite:
8185 case IcsPlayingWhite:
8186 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8188 case TwoMachinesPlay:
8189 if (cps->twoMachinesColor[0] == 'w')
8190 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8192 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8199 } else if (strncmp(message, "opponent mates", 14) == 0) {
8201 case MachinePlaysBlack:
8202 case IcsPlayingBlack:
8203 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8205 case MachinePlaysWhite:
8206 case IcsPlayingWhite:
8207 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8209 case TwoMachinesPlay:
8210 if (cps->twoMachinesColor[0] == 'w')
8211 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8213 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8220 } else if (strncmp(message, "computer mates", 14) == 0) {
8222 case MachinePlaysBlack:
8223 case IcsPlayingBlack:
8224 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8226 case MachinePlaysWhite:
8227 case IcsPlayingWhite:
8228 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8230 case TwoMachinesPlay:
8231 if (cps->twoMachinesColor[0] == 'w')
8232 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8234 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8241 } else if (strncmp(message, "checkmate", 9) == 0) {
8242 if (WhiteOnMove(forwardMostMove)) {
8243 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8245 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8248 } else if (strstr(message, "Draw") != NULL ||
8249 strstr(message, "game is a draw") != NULL) {
8250 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8252 } else if (strstr(message, "offer") != NULL &&
8253 strstr(message, "draw") != NULL) {
8255 if (appData.zippyPlay && first.initDone) {
8256 /* Relay offer to ICS */
8257 SendToICS(ics_prefix);
8258 SendToICS("draw\n");
8261 cps->offeredDraw = 2; /* valid until this engine moves twice */
8262 if (gameMode == TwoMachinesPlay) {
8263 if (cps->other->offeredDraw) {
8264 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8265 /* [HGM] in two-machine mode we delay relaying draw offer */
8266 /* until after we also have move, to see if it is really claim */
8268 } else if (gameMode == MachinePlaysWhite ||
8269 gameMode == MachinePlaysBlack) {
8270 if (userOfferedDraw) {
8271 DisplayInformation(_("Machine accepts your draw offer"));
8272 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8274 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8281 * Look for thinking output
8283 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8284 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8286 int plylev, mvleft, mvtot, curscore, time;
8287 char mvname[MOVE_LEN];
8291 int prefixHint = FALSE;
8292 mvname[0] = NULLCHAR;
8295 case MachinePlaysBlack:
8296 case IcsPlayingBlack:
8297 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8299 case MachinePlaysWhite:
8300 case IcsPlayingWhite:
8301 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8306 case IcsObserving: /* [DM] icsEngineAnalyze */
8307 if (!appData.icsEngineAnalyze) ignore = TRUE;
8309 case TwoMachinesPlay:
8310 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8320 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8322 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8323 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8325 if (plyext != ' ' && plyext != '\t') {
8329 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8330 if( cps->scoreIsAbsolute &&
8331 ( gameMode == MachinePlaysBlack ||
8332 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8333 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8334 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8335 !WhiteOnMove(currentMove)
8338 curscore = -curscore;
8342 tempStats.depth = plylev;
8343 tempStats.nodes = nodes;
8344 tempStats.time = time;
8345 tempStats.score = curscore;
8346 tempStats.got_only_move = 0;
8348 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8351 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8352 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8353 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8354 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8355 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8356 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8357 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8358 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8361 /* Buffer overflow protection */
8362 if (buf1[0] != NULLCHAR) {
8363 if (strlen(buf1) >= sizeof(tempStats.movelist)
8364 && appData.debugMode) {
8366 "PV is too long; using the first %u bytes.\n",
8367 (unsigned) sizeof(tempStats.movelist) - 1);
8370 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8372 sprintf(tempStats.movelist, " no PV\n");
8375 if (tempStats.seen_stat) {
8376 tempStats.ok_to_send = 1;
8379 if (strchr(tempStats.movelist, '(') != NULL) {
8380 tempStats.line_is_book = 1;
8381 tempStats.nr_moves = 0;
8382 tempStats.moves_left = 0;
8384 tempStats.line_is_book = 0;
8387 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8388 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8390 SendProgramStatsToFrontend( cps, &tempStats );
8393 [AS] Protect the thinkOutput buffer from overflow... this
8394 is only useful if buf1 hasn't overflowed first!
8396 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8398 (gameMode == TwoMachinesPlay ?
8399 ToUpper(cps->twoMachinesColor[0]) : ' '),
8400 ((double) curscore) / 100.0,
8401 prefixHint ? lastHint : "",
8402 prefixHint ? " " : "" );
8404 if( buf1[0] != NULLCHAR ) {
8405 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8407 if( strlen(buf1) > max_len ) {
8408 if( appData.debugMode) {
8409 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8411 buf1[max_len+1] = '\0';
8414 strcat( thinkOutput, buf1 );
8417 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8418 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8419 DisplayMove(currentMove - 1);
8423 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8424 /* crafty (9.25+) says "(only move) <move>"
8425 * if there is only 1 legal move
8427 sscanf(p, "(only move) %s", buf1);
8428 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8429 sprintf(programStats.movelist, "%s (only move)", buf1);
8430 programStats.depth = 1;
8431 programStats.nr_moves = 1;
8432 programStats.moves_left = 1;
8433 programStats.nodes = 1;
8434 programStats.time = 1;
8435 programStats.got_only_move = 1;
8437 /* Not really, but we also use this member to
8438 mean "line isn't going to change" (Crafty
8439 isn't searching, so stats won't change) */
8440 programStats.line_is_book = 1;
8442 SendProgramStatsToFrontend( cps, &programStats );
8444 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8445 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8446 DisplayMove(currentMove - 1);
8449 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8450 &time, &nodes, &plylev, &mvleft,
8451 &mvtot, mvname) >= 5) {
8452 /* The stat01: line is from Crafty (9.29+) in response
8453 to the "." command */
8454 programStats.seen_stat = 1;
8455 cps->maybeThinking = TRUE;
8457 if (programStats.got_only_move || !appData.periodicUpdates)
8460 programStats.depth = plylev;
8461 programStats.time = time;
8462 programStats.nodes = nodes;
8463 programStats.moves_left = mvleft;
8464 programStats.nr_moves = mvtot;
8465 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8466 programStats.ok_to_send = 1;
8467 programStats.movelist[0] = '\0';
8469 SendProgramStatsToFrontend( cps, &programStats );
8473 } else if (strncmp(message,"++",2) == 0) {
8474 /* Crafty 9.29+ outputs this */
8475 programStats.got_fail = 2;
8478 } else if (strncmp(message,"--",2) == 0) {
8479 /* Crafty 9.29+ outputs this */
8480 programStats.got_fail = 1;
8483 } else if (thinkOutput[0] != NULLCHAR &&
8484 strncmp(message, " ", 4) == 0) {
8485 unsigned message_len;
8488 while (*p && *p == ' ') p++;
8490 message_len = strlen( p );
8492 /* [AS] Avoid buffer overflow */
8493 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8494 strcat(thinkOutput, " ");
8495 strcat(thinkOutput, p);
8498 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8499 strcat(programStats.movelist, " ");
8500 strcat(programStats.movelist, p);
8503 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8504 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8505 DisplayMove(currentMove - 1);
8513 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8514 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8516 ChessProgramStats cpstats;
8518 if (plyext != ' ' && plyext != '\t') {
8522 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8523 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8524 curscore = -curscore;
8527 cpstats.depth = plylev;
8528 cpstats.nodes = nodes;
8529 cpstats.time = time;
8530 cpstats.score = curscore;
8531 cpstats.got_only_move = 0;
8532 cpstats.movelist[0] = '\0';
8534 if (buf1[0] != NULLCHAR) {
8535 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8538 cpstats.ok_to_send = 0;
8539 cpstats.line_is_book = 0;
8540 cpstats.nr_moves = 0;
8541 cpstats.moves_left = 0;
8543 SendProgramStatsToFrontend( cps, &cpstats );
8550 /* Parse a game score from the character string "game", and
8551 record it as the history of the current game. The game
8552 score is NOT assumed to start from the standard position.
8553 The display is not updated in any way.
8556 ParseGameHistory(game)
8560 int fromX, fromY, toX, toY, boardIndex;
8565 if (appData.debugMode)
8566 fprintf(debugFP, "Parsing game history: %s\n", game);
8568 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8569 gameInfo.site = StrSave(appData.icsHost);
8570 gameInfo.date = PGNDate();
8571 gameInfo.round = StrSave("-");
8573 /* Parse out names of players */
8574 while (*game == ' ') game++;
8576 while (*game != ' ') *p++ = *game++;
8578 gameInfo.white = StrSave(buf);
8579 while (*game == ' ') game++;
8581 while (*game != ' ' && *game != '\n') *p++ = *game++;
8583 gameInfo.black = StrSave(buf);
8586 boardIndex = blackPlaysFirst ? 1 : 0;
8589 yyboardindex = boardIndex;
8590 moveType = (ChessMove) Myylex();
8592 case IllegalMove: /* maybe suicide chess, etc. */
8593 if (appData.debugMode) {
8594 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8595 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8596 setbuf(debugFP, NULL);
8598 case WhitePromotion:
8599 case BlackPromotion:
8600 case WhiteNonPromotion:
8601 case BlackNonPromotion:
8603 case WhiteCapturesEnPassant:
8604 case BlackCapturesEnPassant:
8605 case WhiteKingSideCastle:
8606 case WhiteQueenSideCastle:
8607 case BlackKingSideCastle:
8608 case BlackQueenSideCastle:
8609 case WhiteKingSideCastleWild:
8610 case WhiteQueenSideCastleWild:
8611 case BlackKingSideCastleWild:
8612 case BlackQueenSideCastleWild:
8614 case WhiteHSideCastleFR:
8615 case WhiteASideCastleFR:
8616 case BlackHSideCastleFR:
8617 case BlackASideCastleFR:
8619 fromX = currentMoveString[0] - AAA;
8620 fromY = currentMoveString[1] - ONE;
8621 toX = currentMoveString[2] - AAA;
8622 toY = currentMoveString[3] - ONE;
8623 promoChar = currentMoveString[4];
8627 fromX = moveType == WhiteDrop ?
8628 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8629 (int) CharToPiece(ToLower(currentMoveString[0]));
8631 toX = currentMoveString[2] - AAA;
8632 toY = currentMoveString[3] - ONE;
8633 promoChar = NULLCHAR;
8637 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8638 if (appData.debugMode) {
8639 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8640 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8641 setbuf(debugFP, NULL);
8643 DisplayError(buf, 0);
8645 case ImpossibleMove:
8647 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8648 if (appData.debugMode) {
8649 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8650 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8651 setbuf(debugFP, NULL);
8653 DisplayError(buf, 0);
8656 if (boardIndex < backwardMostMove) {
8657 /* Oops, gap. How did that happen? */
8658 DisplayError(_("Gap in move list"), 0);
8661 backwardMostMove = blackPlaysFirst ? 1 : 0;
8662 if (boardIndex > forwardMostMove) {
8663 forwardMostMove = boardIndex;
8667 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8668 strcat(parseList[boardIndex-1], " ");
8669 strcat(parseList[boardIndex-1], yy_text);
8681 case GameUnfinished:
8682 if (gameMode == IcsExamining) {
8683 if (boardIndex < backwardMostMove) {
8684 /* Oops, gap. How did that happen? */
8687 backwardMostMove = blackPlaysFirst ? 1 : 0;
8690 gameInfo.result = moveType;
8691 p = strchr(yy_text, '{');
8692 if (p == NULL) p = strchr(yy_text, '(');
8695 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8697 q = strchr(p, *p == '{' ? '}' : ')');
8698 if (q != NULL) *q = NULLCHAR;
8701 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8702 gameInfo.resultDetails = StrSave(p);
8705 if (boardIndex >= forwardMostMove &&
8706 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8707 backwardMostMove = blackPlaysFirst ? 1 : 0;
8710 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8711 fromY, fromX, toY, toX, promoChar,
8712 parseList[boardIndex]);
8713 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8714 /* currentMoveString is set as a side-effect of yylex */
8715 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8716 strcat(moveList[boardIndex], "\n");
8718 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8719 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8725 if(gameInfo.variant != VariantShogi)
8726 strcat(parseList[boardIndex - 1], "+");
8730 strcat(parseList[boardIndex - 1], "#");
8737 /* Apply a move to the given board */
8739 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8740 int fromX, fromY, toX, toY;
8744 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8745 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8747 /* [HGM] compute & store e.p. status and castling rights for new position */
8748 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8750 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8751 oldEP = (signed char)board[EP_STATUS];
8752 board[EP_STATUS] = EP_NONE;
8754 if( board[toY][toX] != EmptySquare )
8755 board[EP_STATUS] = EP_CAPTURE;
8757 if (fromY == DROP_RANK) {
8759 piece = board[toY][toX] = (ChessSquare) fromX;
8763 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8764 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8765 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8767 if( board[fromY][fromX] == WhitePawn ) {
8768 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8769 board[EP_STATUS] = EP_PAWN_MOVE;
8771 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8772 gameInfo.variant != VariantBerolina || toX < fromX)
8773 board[EP_STATUS] = toX | berolina;
8774 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8775 gameInfo.variant != VariantBerolina || toX > fromX)
8776 board[EP_STATUS] = toX;
8779 if( board[fromY][fromX] == BlackPawn ) {
8780 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8781 board[EP_STATUS] = EP_PAWN_MOVE;
8782 if( toY-fromY== -2) {
8783 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8784 gameInfo.variant != VariantBerolina || toX < fromX)
8785 board[EP_STATUS] = toX | berolina;
8786 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8787 gameInfo.variant != VariantBerolina || toX > fromX)
8788 board[EP_STATUS] = toX;
8792 for(i=0; i<nrCastlingRights; i++) {
8793 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8794 board[CASTLING][i] == toX && castlingRank[i] == toY
8795 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8798 if (fromX == toX && fromY == toY) return;
8800 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8801 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8802 if(gameInfo.variant == VariantKnightmate)
8803 king += (int) WhiteUnicorn - (int) WhiteKing;
8805 /* Code added by Tord: */
8806 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8807 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8808 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8809 board[fromY][fromX] = EmptySquare;
8810 board[toY][toX] = EmptySquare;
8811 if((toX > fromX) != (piece == WhiteRook)) {
8812 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8814 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8816 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8817 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8818 board[fromY][fromX] = EmptySquare;
8819 board[toY][toX] = EmptySquare;
8820 if((toX > fromX) != (piece == BlackRook)) {
8821 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8823 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8825 /* End of code added by Tord */
8827 } else if (board[fromY][fromX] == king
8828 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8829 && toY == fromY && toX > fromX+1) {
8830 board[fromY][fromX] = EmptySquare;
8831 board[toY][toX] = king;
8832 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8833 board[fromY][BOARD_RGHT-1] = EmptySquare;
8834 } else if (board[fromY][fromX] == king
8835 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8836 && toY == fromY && toX < fromX-1) {
8837 board[fromY][fromX] = EmptySquare;
8838 board[toY][toX] = king;
8839 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8840 board[fromY][BOARD_LEFT] = EmptySquare;
8841 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8842 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8843 && toY >= BOARD_HEIGHT-promoRank
8845 /* white pawn promotion */
8846 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8847 if (board[toY][toX] == EmptySquare) {
8848 board[toY][toX] = WhiteQueen;
8850 if(gameInfo.variant==VariantBughouse ||
8851 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8852 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8853 board[fromY][fromX] = EmptySquare;
8854 } else if ((fromY == BOARD_HEIGHT-4)
8856 && gameInfo.variant != VariantXiangqi
8857 && gameInfo.variant != VariantBerolina
8858 && (board[fromY][fromX] == WhitePawn)
8859 && (board[toY][toX] == EmptySquare)) {
8860 board[fromY][fromX] = EmptySquare;
8861 board[toY][toX] = WhitePawn;
8862 captured = board[toY - 1][toX];
8863 board[toY - 1][toX] = EmptySquare;
8864 } else if ((fromY == BOARD_HEIGHT-4)
8866 && gameInfo.variant == VariantBerolina
8867 && (board[fromY][fromX] == WhitePawn)
8868 && (board[toY][toX] == EmptySquare)) {
8869 board[fromY][fromX] = EmptySquare;
8870 board[toY][toX] = WhitePawn;
8871 if(oldEP & EP_BEROLIN_A) {
8872 captured = board[fromY][fromX-1];
8873 board[fromY][fromX-1] = EmptySquare;
8874 }else{ captured = board[fromY][fromX+1];
8875 board[fromY][fromX+1] = EmptySquare;
8877 } else if (board[fromY][fromX] == king
8878 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8879 && toY == fromY && toX > fromX+1) {
8880 board[fromY][fromX] = EmptySquare;
8881 board[toY][toX] = king;
8882 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8883 board[fromY][BOARD_RGHT-1] = EmptySquare;
8884 } else if (board[fromY][fromX] == king
8885 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8886 && toY == fromY && toX < fromX-1) {
8887 board[fromY][fromX] = EmptySquare;
8888 board[toY][toX] = king;
8889 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8890 board[fromY][BOARD_LEFT] = EmptySquare;
8891 } else if (fromY == 7 && fromX == 3
8892 && board[fromY][fromX] == BlackKing
8893 && toY == 7 && toX == 5) {
8894 board[fromY][fromX] = EmptySquare;
8895 board[toY][toX] = BlackKing;
8896 board[fromY][7] = EmptySquare;
8897 board[toY][4] = BlackRook;
8898 } else if (fromY == 7 && fromX == 3
8899 && board[fromY][fromX] == BlackKing
8900 && toY == 7 && toX == 1) {
8901 board[fromY][fromX] = EmptySquare;
8902 board[toY][toX] = BlackKing;
8903 board[fromY][0] = EmptySquare;
8904 board[toY][2] = BlackRook;
8905 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8906 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8909 /* black pawn promotion */
8910 board[toY][toX] = CharToPiece(ToLower(promoChar));
8911 if (board[toY][toX] == EmptySquare) {
8912 board[toY][toX] = BlackQueen;
8914 if(gameInfo.variant==VariantBughouse ||
8915 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8916 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8917 board[fromY][fromX] = EmptySquare;
8918 } else if ((fromY == 3)
8920 && gameInfo.variant != VariantXiangqi
8921 && gameInfo.variant != VariantBerolina
8922 && (board[fromY][fromX] == BlackPawn)
8923 && (board[toY][toX] == EmptySquare)) {
8924 board[fromY][fromX] = EmptySquare;
8925 board[toY][toX] = BlackPawn;
8926 captured = board[toY + 1][toX];
8927 board[toY + 1][toX] = EmptySquare;
8928 } else if ((fromY == 3)
8930 && gameInfo.variant == VariantBerolina
8931 && (board[fromY][fromX] == BlackPawn)
8932 && (board[toY][toX] == EmptySquare)) {
8933 board[fromY][fromX] = EmptySquare;
8934 board[toY][toX] = BlackPawn;
8935 if(oldEP & EP_BEROLIN_A) {
8936 captured = board[fromY][fromX-1];
8937 board[fromY][fromX-1] = EmptySquare;
8938 }else{ captured = board[fromY][fromX+1];
8939 board[fromY][fromX+1] = EmptySquare;
8942 board[toY][toX] = board[fromY][fromX];
8943 board[fromY][fromX] = EmptySquare;
8947 if (gameInfo.holdingsWidth != 0) {
8949 /* !!A lot more code needs to be written to support holdings */
8950 /* [HGM] OK, so I have written it. Holdings are stored in the */
8951 /* penultimate board files, so they are automaticlly stored */
8952 /* in the game history. */
8953 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8954 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8955 /* Delete from holdings, by decreasing count */
8956 /* and erasing image if necessary */
8957 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8958 if(p < (int) BlackPawn) { /* white drop */
8959 p -= (int)WhitePawn;
8960 p = PieceToNumber((ChessSquare)p);
8961 if(p >= gameInfo.holdingsSize) p = 0;
8962 if(--board[p][BOARD_WIDTH-2] <= 0)
8963 board[p][BOARD_WIDTH-1] = EmptySquare;
8964 if((int)board[p][BOARD_WIDTH-2] < 0)
8965 board[p][BOARD_WIDTH-2] = 0;
8966 } else { /* black drop */
8967 p -= (int)BlackPawn;
8968 p = PieceToNumber((ChessSquare)p);
8969 if(p >= gameInfo.holdingsSize) p = 0;
8970 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8971 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8972 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8973 board[BOARD_HEIGHT-1-p][1] = 0;
8976 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8977 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8978 /* [HGM] holdings: Add to holdings, if holdings exist */
8979 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8980 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8981 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8984 if (p >= (int) BlackPawn) {
8985 p -= (int)BlackPawn;
8986 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8987 /* in Shogi restore piece to its original first */
8988 captured = (ChessSquare) (DEMOTED captured);
8991 p = PieceToNumber((ChessSquare)p);
8992 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8993 board[p][BOARD_WIDTH-2]++;
8994 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8996 p -= (int)WhitePawn;
8997 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8998 captured = (ChessSquare) (DEMOTED captured);
9001 p = PieceToNumber((ChessSquare)p);
9002 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9003 board[BOARD_HEIGHT-1-p][1]++;
9004 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9007 } else if (gameInfo.variant == VariantAtomic) {
9008 if (captured != EmptySquare) {
9010 for (y = toY-1; y <= toY+1; y++) {
9011 for (x = toX-1; x <= toX+1; x++) {
9012 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9013 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9014 board[y][x] = EmptySquare;
9018 board[toY][toX] = EmptySquare;
9021 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9022 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9024 if(promoChar == '+') {
9025 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9026 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9027 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9028 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9030 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9031 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9032 // [HGM] superchess: take promotion piece out of holdings
9033 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9034 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9035 if(!--board[k][BOARD_WIDTH-2])
9036 board[k][BOARD_WIDTH-1] = EmptySquare;
9038 if(!--board[BOARD_HEIGHT-1-k][1])
9039 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9045 /* Updates forwardMostMove */
9047 MakeMove(fromX, fromY, toX, toY, promoChar)
9048 int fromX, fromY, toX, toY;
9051 // forwardMostMove++; // [HGM] bare: moved downstream
9053 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9054 int timeLeft; static int lastLoadFlag=0; int king, piece;
9055 piece = boards[forwardMostMove][fromY][fromX];
9056 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9057 if(gameInfo.variant == VariantKnightmate)
9058 king += (int) WhiteUnicorn - (int) WhiteKing;
9059 if(forwardMostMove == 0) {
9061 fprintf(serverMoves, "%s;", second.tidy);
9062 fprintf(serverMoves, "%s;", first.tidy);
9063 if(!blackPlaysFirst)
9064 fprintf(serverMoves, "%s;", second.tidy);
9065 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9066 lastLoadFlag = loadFlag;
9068 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9069 // print castling suffix
9070 if( toY == fromY && piece == king ) {
9072 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9074 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9077 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9078 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9079 boards[forwardMostMove][toY][toX] == EmptySquare
9080 && fromX != toX && fromY != toY)
9081 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9083 if(promoChar != NULLCHAR)
9084 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9086 fprintf(serverMoves, "/%d/%d",
9087 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9088 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9089 else timeLeft = blackTimeRemaining/1000;
9090 fprintf(serverMoves, "/%d", timeLeft);
9092 fflush(serverMoves);
9095 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9096 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9100 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9101 if (commentList[forwardMostMove+1] != NULL) {
9102 free(commentList[forwardMostMove+1]);
9103 commentList[forwardMostMove+1] = NULL;
9105 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9106 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9107 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9108 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9109 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9110 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9111 gameInfo.result = GameUnfinished;
9112 if (gameInfo.resultDetails != NULL) {
9113 free(gameInfo.resultDetails);
9114 gameInfo.resultDetails = NULL;
9116 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9117 moveList[forwardMostMove - 1]);
9118 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9119 PosFlags(forwardMostMove - 1),
9120 fromY, fromX, toY, toX, promoChar,
9121 parseList[forwardMostMove - 1]);
9122 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9128 if(gameInfo.variant != VariantShogi)
9129 strcat(parseList[forwardMostMove - 1], "+");
9133 strcat(parseList[forwardMostMove - 1], "#");
9136 if (appData.debugMode) {
9137 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9142 /* Updates currentMove if not pausing */
9144 ShowMove(fromX, fromY, toX, toY)
9146 int instant = (gameMode == PlayFromGameFile) ?
9147 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9148 if(appData.noGUI) return;
9149 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9151 if (forwardMostMove == currentMove + 1) {
9152 AnimateMove(boards[forwardMostMove - 1],
9153 fromX, fromY, toX, toY);
9155 if (appData.highlightLastMove) {
9156 SetHighlights(fromX, fromY, toX, toY);
9159 currentMove = forwardMostMove;
9162 if (instant) return;
9164 DisplayMove(currentMove - 1);
9165 DrawPosition(FALSE, boards[currentMove]);
9166 DisplayBothClocks();
9167 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9170 void SendEgtPath(ChessProgramState *cps)
9171 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9172 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9174 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9177 char c, *q = name+1, *r, *s;
9179 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9180 while(*p && *p != ',') *q++ = *p++;
9182 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9183 strcmp(name, ",nalimov:") == 0 ) {
9184 // take nalimov path from the menu-changeable option first, if it is defined
9185 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9186 SendToProgram(buf,cps); // send egtbpath command for nalimov
9188 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9189 (s = StrStr(appData.egtFormats, name)) != NULL) {
9190 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9191 s = r = StrStr(s, ":") + 1; // beginning of path info
9192 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9193 c = *r; *r = 0; // temporarily null-terminate path info
9194 *--q = 0; // strip of trailig ':' from name
9195 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9197 SendToProgram(buf,cps); // send egtbpath command for this format
9199 if(*p == ',') p++; // read away comma to position for next format name
9204 InitChessProgram(cps, setup)
9205 ChessProgramState *cps;
9206 int setup; /* [HGM] needed to setup FRC opening position */
9208 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9209 if (appData.noChessProgram) return;
9210 hintRequested = FALSE;
9211 bookRequested = FALSE;
9213 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9214 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9215 if(cps->memSize) { /* [HGM] memory */
9216 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9217 SendToProgram(buf, cps);
9219 SendEgtPath(cps); /* [HGM] EGT */
9220 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9221 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9222 SendToProgram(buf, cps);
9225 SendToProgram(cps->initString, cps);
9226 if (gameInfo.variant != VariantNormal &&
9227 gameInfo.variant != VariantLoadable
9228 /* [HGM] also send variant if board size non-standard */
9229 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9231 char *v = VariantName(gameInfo.variant);
9232 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9233 /* [HGM] in protocol 1 we have to assume all variants valid */
9234 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9235 DisplayFatalError(buf, 0, 1);
9239 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9240 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9241 if( gameInfo.variant == VariantXiangqi )
9242 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9243 if( gameInfo.variant == VariantShogi )
9244 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9245 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9246 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9247 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9248 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9249 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9250 if( gameInfo.variant == VariantCourier )
9251 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9252 if( gameInfo.variant == VariantSuper )
9253 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9254 if( gameInfo.variant == VariantGreat )
9255 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9256 if( gameInfo.variant == VariantSChess )
9257 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9260 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9261 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9262 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9263 if(StrStr(cps->variants, b) == NULL) {
9264 // specific sized variant not known, check if general sizing allowed
9265 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9266 if(StrStr(cps->variants, "boardsize") == NULL) {
9267 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9268 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9269 DisplayFatalError(buf, 0, 1);
9272 /* [HGM] here we really should compare with the maximum supported board size */
9275 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9276 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9277 SendToProgram(buf, cps);
9279 currentlyInitializedVariant = gameInfo.variant;
9281 /* [HGM] send opening position in FRC to first engine */
9283 SendToProgram("force\n", cps);
9285 /* engine is now in force mode! Set flag to wake it up after first move. */
9286 setboardSpoiledMachineBlack = 1;
9290 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9291 SendToProgram(buf, cps);
9293 cps->maybeThinking = FALSE;
9294 cps->offeredDraw = 0;
9295 if (!appData.icsActive) {
9296 SendTimeControl(cps, movesPerSession, timeControl,
9297 timeIncrement, appData.searchDepth,
9300 if (appData.showThinking
9301 // [HGM] thinking: four options require thinking output to be sent
9302 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9304 SendToProgram("post\n", cps);
9306 SendToProgram("hard\n", cps);
9307 if (!appData.ponderNextMove) {
9308 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9309 it without being sure what state we are in first. "hard"
9310 is not a toggle, so that one is OK.
9312 SendToProgram("easy\n", cps);
9315 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9316 SendToProgram(buf, cps);
9318 cps->initDone = TRUE;
9323 StartChessProgram(cps)
9324 ChessProgramState *cps;
9329 if (appData.noChessProgram) return;
9330 cps->initDone = FALSE;
9332 if (strcmp(cps->host, "localhost") == 0) {
9333 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9334 } else if (*appData.remoteShell == NULLCHAR) {
9335 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9337 if (*appData.remoteUser == NULLCHAR) {
9338 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9341 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9342 cps->host, appData.remoteUser, cps->program);
9344 err = StartChildProcess(buf, "", &cps->pr);
9348 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9349 DisplayFatalError(buf, err, 1);
9355 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9356 if (cps->protocolVersion > 1) {
9357 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9358 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9359 cps->comboCnt = 0; // and values of combo boxes
9360 SendToProgram(buf, cps);
9362 SendToProgram("xboard\n", cps);
9367 TwoMachinesEventIfReady P((void))
9369 static int curMess = 0;
9370 if (first.lastPing != first.lastPong) {
9371 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9372 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9375 if (second.lastPing != second.lastPong) {
9376 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9377 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9380 DisplayMessage("", ""); curMess = 0;
9386 CreateTourney(char *name)
9389 if(name[0] == NULLCHAR) return 0;
9390 f = fopen(appData.tourneyFile, "r");
9391 if(f) { // file exists
9392 ParseArgsFromFile(f); // parse it
9394 f = fopen(appData.tourneyFile, "w");
9395 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9396 // create a file with tournament description
9397 fprintf(f, "-participants {%s}\n", appData.participants);
9398 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9399 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9400 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9401 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9402 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9403 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9404 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9405 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9406 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9407 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9408 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9409 fprintf(f, "-results \"\"\n");
9413 appData.noChessProgram = FALSE;
9414 appData.clockMode = TRUE;
9419 #define MAXENGINES 1000
9420 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9422 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9424 char buf[MSG_SIZ], *p, *q;
9428 while(*p && *p != '\n') *q++ = *p++;
9430 if(engineList[i]) free(engineList[i]);
9431 engineList[i] = strdup(buf);
9433 TidyProgramName(engineList[i], "localhost", buf);
9434 if(engineMnemonic[i]) free(engineMnemonic[i]);
9435 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9437 sscanf(q + 8, "%s", buf + strlen(buf));
9440 engineMnemonic[i] = strdup(buf);
9442 if(i > MAXENGINES - 2) break;
9444 engineList[i] = NULL;
9447 // following implemented as macro to avoid type limitations
9448 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9450 void SwapEngines(int n)
9451 { // swap settings for first engine and other engine (so far only some selected options)
9456 SWAP(chessProgram, p)
9458 SWAP(hasOwnBookUCI, h)
9459 SWAP(protocolVersion, h)
9461 SWAP(scoreIsAbsolute, h)
9467 SetPlayer(int player)
9468 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9470 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9471 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9472 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9473 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9474 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9475 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9477 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9478 ParseArgsFromString(resetOptions);
9479 ParseArgsFromString(buf);
9485 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9486 { // determine players from game number
9487 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9489 if(appData.tourneyType == 0) {
9490 roundsPerCycle = (nPlayers - 1) | 1;
9491 pairingsPerRound = nPlayers / 2;
9492 } else if(appData.tourneyType > 0) {
9493 roundsPerCycle = nPlayers - appData.tourneyType;
9494 pairingsPerRound = appData.tourneyType;
9496 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9497 gamesPerCycle = gamesPerRound * roundsPerCycle;
9498 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9499 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9500 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9501 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9502 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9503 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9505 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9506 if(appData.roundSync) *syncInterval = gamesPerRound;
9508 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9510 if(appData.tourneyType == 0) {
9511 if(curPairing == (nPlayers-1)/2 ) {
9512 *whitePlayer = curRound;
9513 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9515 *whitePlayer = curRound - pairingsPerRound + curPairing;
9516 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9517 *blackPlayer = curRound + pairingsPerRound - curPairing;
9518 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9520 } else if(appData.tourneyType > 0) {
9521 *whitePlayer = curPairing;
9522 *blackPlayer = curRound + appData.tourneyType;
9525 // take care of white/black alternation per round.
9526 // For cycles and games this is already taken care of by default, derived from matchGame!
9527 return curRound & 1;
9531 NextTourneyGame(int nr, int *swapColors)
9532 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9534 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9536 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9537 tf = fopen(appData.tourneyFile, "r");
9538 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9539 ParseArgsFromFile(tf); fclose(tf);
9541 p = appData.participants;
9542 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9543 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9546 p = q = appData.results;
9547 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9548 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9549 DisplayMessage(_("Waiting for other game(s)"),"");
9550 waitingForGame = TRUE;
9551 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9554 waitingForGame = FALSE;
9557 if(first.pr != NoProc) return 1; // engines already loaded
9559 // redefine engines, engine dir, etc.
9560 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9561 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9563 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9564 SwapEngines(1); // and make that valid for second engine by swapping
9565 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9566 InitEngine(&second, 1);
9567 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9573 { // performs game initialization that does not invoke engines, and then tries to start the game
9574 int firstWhite, swapColors = 0;
9575 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9576 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9577 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9578 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9579 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9580 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9581 Reset(FALSE, first.pr != NoProc);
9582 appData.noChessProgram = FALSE;
9583 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9587 void UserAdjudicationEvent( int result )
9589 ChessMove gameResult = GameIsDrawn;
9592 gameResult = WhiteWins;
9594 else if( result < 0 ) {
9595 gameResult = BlackWins;
9598 if( gameMode == TwoMachinesPlay ) {
9599 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9604 // [HGM] save: calculate checksum of game to make games easily identifiable
9605 int StringCheckSum(char *s)
9608 if(s==NULL) return 0;
9609 while(*s) i = i*259 + *s++;
9616 for(i=backwardMostMove; i<forwardMostMove; i++) {
9617 sum += pvInfoList[i].depth;
9618 sum += StringCheckSum(parseList[i]);
9619 sum += StringCheckSum(commentList[i]);
9622 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9623 return sum + StringCheckSum(commentList[i]);
9624 } // end of save patch
9627 GameEnds(result, resultDetails, whosays)
9629 char *resultDetails;
9632 GameMode nextGameMode;
9634 char buf[MSG_SIZ], popupRequested = 0, forceUnload;
9636 if(endingGame) return; /* [HGM] crash: forbid recursion */
9638 if(twoBoards) { // [HGM] dual: switch back to one board
9639 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9640 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9642 if (appData.debugMode) {
9643 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9644 result, resultDetails ? resultDetails : "(null)", whosays);
9647 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9649 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9650 /* If we are playing on ICS, the server decides when the
9651 game is over, but the engine can offer to draw, claim
9655 if (appData.zippyPlay && first.initDone) {
9656 if (result == GameIsDrawn) {
9657 /* In case draw still needs to be claimed */
9658 SendToICS(ics_prefix);
9659 SendToICS("draw\n");
9660 } else if (StrCaseStr(resultDetails, "resign")) {
9661 SendToICS(ics_prefix);
9662 SendToICS("resign\n");
9666 endingGame = 0; /* [HGM] crash */
9670 /* If we're loading the game from a file, stop */
9671 if (whosays == GE_FILE) {
9672 (void) StopLoadGameTimer();
9676 /* Cancel draw offers */
9677 first.offeredDraw = second.offeredDraw = 0;
9679 /* If this is an ICS game, only ICS can really say it's done;
9680 if not, anyone can. */
9681 isIcsGame = (gameMode == IcsPlayingWhite ||
9682 gameMode == IcsPlayingBlack ||
9683 gameMode == IcsObserving ||
9684 gameMode == IcsExamining);
9686 if (!isIcsGame || whosays == GE_ICS) {
9687 /* OK -- not an ICS game, or ICS said it was done */
9689 if (!isIcsGame && !appData.noChessProgram)
9690 SetUserThinkingEnables();
9692 /* [HGM] if a machine claims the game end we verify this claim */
9693 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9694 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9696 ChessMove trueResult = (ChessMove) -1;
9698 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9699 first.twoMachinesColor[0] :
9700 second.twoMachinesColor[0] ;
9702 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9703 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9704 /* [HGM] verify: engine mate claims accepted if they were flagged */
9705 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9707 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9708 /* [HGM] verify: engine mate claims accepted if they were flagged */
9709 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9711 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9712 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9715 // now verify win claims, but not in drop games, as we don't understand those yet
9716 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9717 || gameInfo.variant == VariantGreat) &&
9718 (result == WhiteWins && claimer == 'w' ||
9719 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9720 if (appData.debugMode) {
9721 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9722 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9724 if(result != trueResult) {
9725 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9726 result = claimer == 'w' ? BlackWins : WhiteWins;
9727 resultDetails = buf;
9730 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9731 && (forwardMostMove <= backwardMostMove ||
9732 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9733 (claimer=='b')==(forwardMostMove&1))
9735 /* [HGM] verify: draws that were not flagged are false claims */
9736 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9737 result = claimer == 'w' ? BlackWins : WhiteWins;
9738 resultDetails = buf;
9740 /* (Claiming a loss is accepted no questions asked!) */
9742 /* [HGM] bare: don't allow bare King to win */
9743 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9744 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9745 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9746 && result != GameIsDrawn)
9747 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9748 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9749 int p = (signed char)boards[forwardMostMove][i][j] - color;
9750 if(p >= 0 && p <= (int)WhiteKing) k++;
9752 if (appData.debugMode) {
9753 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9754 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9757 result = GameIsDrawn;
9758 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9759 resultDetails = buf;
9765 if(serverMoves != NULL && !loadFlag) { char c = '=';
9766 if(result==WhiteWins) c = '+';
9767 if(result==BlackWins) c = '-';
9768 if(resultDetails != NULL)
9769 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9771 if (resultDetails != NULL) {
9772 gameInfo.result = result;
9773 gameInfo.resultDetails = StrSave(resultDetails);
9775 /* display last move only if game was not loaded from file */
9776 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9777 DisplayMove(currentMove - 1);
9779 if (forwardMostMove != 0) {
9780 if (gameMode != PlayFromGameFile && gameMode != EditGame
9781 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9783 if (*appData.saveGameFile != NULLCHAR) {
9784 SaveGameToFile(appData.saveGameFile, TRUE);
9785 } else if (appData.autoSaveGames) {
9788 if (*appData.savePositionFile != NULLCHAR) {
9789 SavePositionToFile(appData.savePositionFile);
9794 /* Tell program how game ended in case it is learning */
9795 /* [HGM] Moved this to after saving the PGN, just in case */
9796 /* engine died and we got here through time loss. In that */
9797 /* case we will get a fatal error writing the pipe, which */
9798 /* would otherwise lose us the PGN. */
9799 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9800 /* output during GameEnds should never be fatal anymore */
9801 if (gameMode == MachinePlaysWhite ||
9802 gameMode == MachinePlaysBlack ||
9803 gameMode == TwoMachinesPlay ||
9804 gameMode == IcsPlayingWhite ||
9805 gameMode == IcsPlayingBlack ||
9806 gameMode == BeginningOfGame) {
9808 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9810 if (first.pr != NoProc) {
9811 SendToProgram(buf, &first);
9813 if (second.pr != NoProc &&
9814 gameMode == TwoMachinesPlay) {
9815 SendToProgram(buf, &second);
9820 if (appData.icsActive) {
9821 if (appData.quietPlay &&
9822 (gameMode == IcsPlayingWhite ||
9823 gameMode == IcsPlayingBlack)) {
9824 SendToICS(ics_prefix);
9825 SendToICS("set shout 1\n");
9827 nextGameMode = IcsIdle;
9828 ics_user_moved = FALSE;
9829 /* clean up premove. It's ugly when the game has ended and the
9830 * premove highlights are still on the board.
9834 ClearPremoveHighlights();
9835 DrawPosition(FALSE, boards[currentMove]);
9837 if (whosays == GE_ICS) {
9840 if (gameMode == IcsPlayingWhite)
9842 else if(gameMode == IcsPlayingBlack)
9846 if (gameMode == IcsPlayingBlack)
9848 else if(gameMode == IcsPlayingWhite)
9855 PlayIcsUnfinishedSound();
9858 } else if (gameMode == EditGame ||
9859 gameMode == PlayFromGameFile ||
9860 gameMode == AnalyzeMode ||
9861 gameMode == AnalyzeFile) {
9862 nextGameMode = gameMode;
9864 nextGameMode = EndOfGame;
9869 nextGameMode = gameMode;
9872 if (appData.noChessProgram) {
9873 gameMode = nextGameMode;
9875 endingGame = 0; /* [HGM] crash */
9880 /* Put first chess program into idle state */
9881 if (first.pr != NoProc &&
9882 (gameMode == MachinePlaysWhite ||
9883 gameMode == MachinePlaysBlack ||
9884 gameMode == TwoMachinesPlay ||
9885 gameMode == IcsPlayingWhite ||
9886 gameMode == IcsPlayingBlack ||
9887 gameMode == BeginningOfGame)) {
9888 SendToProgram("force\n", &first);
9889 if (first.usePing) {
9891 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9892 SendToProgram(buf, &first);
9895 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9896 /* Kill off first chess program */
9897 if (first.isr != NULL)
9898 RemoveInputSource(first.isr);
9901 if (first.pr != NoProc) {
9903 DoSleep( appData.delayBeforeQuit );
9904 SendToProgram("quit\n", &first);
9905 DoSleep( appData.delayAfterQuit );
9906 DestroyChildProcess(first.pr, first.useSigterm);
9911 /* Put second chess program into idle state */
9912 if (second.pr != NoProc &&
9913 gameMode == TwoMachinesPlay) {
9914 SendToProgram("force\n", &second);
9915 if (second.usePing) {
9917 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9918 SendToProgram(buf, &second);
9921 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9922 /* Kill off second chess program */
9923 if (second.isr != NULL)
9924 RemoveInputSource(second.isr);
9927 if (second.pr != NoProc) {
9928 DoSleep( appData.delayBeforeQuit );
9929 SendToProgram("quit\n", &second);
9930 DoSleep( appData.delayAfterQuit );
9931 DestroyChildProcess(second.pr, second.useSigterm);
9936 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9941 if (first.twoMachinesColor[0] == 'w') {
9949 if (first.twoMachinesColor[0] == 'b') {
9955 case GameUnfinished:
9961 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
9962 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
9963 ReserveGame(nextGame, resChar); // sets nextGame
9964 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0; // tourney is done
9965 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
9967 if (nextGame <= appData.matchGames) {
9968 gameMode = nextGameMode;
9969 matchGame = nextGame; // this will be overruled in tourney mode!
9970 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
9971 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
9972 endingGame = 0; /* [HGM] crash */
9975 gameMode = nextGameMode;
9976 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9977 first.tidy, second.tidy,
9978 first.matchWins, second.matchWins,
9979 appData.matchGames - (first.matchWins + second.matchWins));
9980 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9981 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9982 first.twoMachinesColor = "black\n";
9983 second.twoMachinesColor = "white\n";
9985 first.twoMachinesColor = "white\n";
9986 second.twoMachinesColor = "black\n";
9990 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9991 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9993 gameMode = nextGameMode;
9995 endingGame = 0; /* [HGM] crash */
9996 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9997 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9998 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10004 /* Assumes program was just initialized (initString sent).
10005 Leaves program in force mode. */
10007 FeedMovesToProgram(cps, upto)
10008 ChessProgramState *cps;
10013 if (appData.debugMode)
10014 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10015 startedFromSetupPosition ? "position and " : "",
10016 backwardMostMove, upto, cps->which);
10017 if(currentlyInitializedVariant != gameInfo.variant) {
10019 // [HGM] variantswitch: make engine aware of new variant
10020 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10021 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10022 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10023 SendToProgram(buf, cps);
10024 currentlyInitializedVariant = gameInfo.variant;
10026 SendToProgram("force\n", cps);
10027 if (startedFromSetupPosition) {
10028 SendBoard(cps, backwardMostMove);
10029 if (appData.debugMode) {
10030 fprintf(debugFP, "feedMoves\n");
10033 for (i = backwardMostMove; i < upto; i++) {
10034 SendMoveToProgram(i, cps);
10040 ResurrectChessProgram()
10042 /* The chess program may have exited.
10043 If so, restart it and feed it all the moves made so far. */
10044 static int doInit = 0;
10046 if (appData.noChessProgram) return 1;
10048 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10049 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10050 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10051 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10053 if (first.pr != NoProc) return 1;
10054 StartChessProgram(&first);
10056 InitChessProgram(&first, FALSE);
10057 FeedMovesToProgram(&first, currentMove);
10059 if (!first.sendTime) {
10060 /* can't tell gnuchess what its clock should read,
10061 so we bow to its notion. */
10063 timeRemaining[0][currentMove] = whiteTimeRemaining;
10064 timeRemaining[1][currentMove] = blackTimeRemaining;
10067 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10068 appData.icsEngineAnalyze) && first.analysisSupport) {
10069 SendToProgram("analyze\n", &first);
10070 first.analyzing = TRUE;
10076 * Button procedures
10079 Reset(redraw, init)
10084 if (appData.debugMode) {
10085 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10086 redraw, init, gameMode);
10088 CleanupTail(); // [HGM] vari: delete any stored variations
10089 pausing = pauseExamInvalid = FALSE;
10090 startedFromSetupPosition = blackPlaysFirst = FALSE;
10092 whiteFlag = blackFlag = FALSE;
10093 userOfferedDraw = FALSE;
10094 hintRequested = bookRequested = FALSE;
10095 first.maybeThinking = FALSE;
10096 second.maybeThinking = FALSE;
10097 first.bookSuspend = FALSE; // [HGM] book
10098 second.bookSuspend = FALSE;
10099 thinkOutput[0] = NULLCHAR;
10100 lastHint[0] = NULLCHAR;
10101 ClearGameInfo(&gameInfo);
10102 gameInfo.variant = StringToVariant(appData.variant);
10103 ics_user_moved = ics_clock_paused = FALSE;
10104 ics_getting_history = H_FALSE;
10106 white_holding[0] = black_holding[0] = NULLCHAR;
10107 ClearProgramStats();
10108 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10112 flipView = appData.flipView;
10113 ClearPremoveHighlights();
10114 gotPremove = FALSE;
10115 alarmSounded = FALSE;
10117 GameEnds(EndOfFile, NULL, GE_PLAYER);
10118 if(appData.serverMovesName != NULL) {
10119 /* [HGM] prepare to make moves file for broadcasting */
10120 clock_t t = clock();
10121 if(serverMoves != NULL) fclose(serverMoves);
10122 serverMoves = fopen(appData.serverMovesName, "r");
10123 if(serverMoves != NULL) {
10124 fclose(serverMoves);
10125 /* delay 15 sec before overwriting, so all clients can see end */
10126 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10128 serverMoves = fopen(appData.serverMovesName, "w");
10132 gameMode = BeginningOfGame;
10134 if(appData.icsActive) gameInfo.variant = VariantNormal;
10135 currentMove = forwardMostMove = backwardMostMove = 0;
10136 InitPosition(redraw);
10137 for (i = 0; i < MAX_MOVES; i++) {
10138 if (commentList[i] != NULL) {
10139 free(commentList[i]);
10140 commentList[i] = NULL;
10144 timeRemaining[0][0] = whiteTimeRemaining;
10145 timeRemaining[1][0] = blackTimeRemaining;
10147 if (first.pr == NULL) {
10148 StartChessProgram(&first);
10151 InitChessProgram(&first, startedFromSetupPosition);
10154 DisplayMessage("", "");
10155 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10156 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10163 if (!AutoPlayOneMove())
10165 if (matchMode || appData.timeDelay == 0)
10167 if (appData.timeDelay < 0)
10169 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10178 int fromX, fromY, toX, toY;
10180 if (appData.debugMode) {
10181 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10184 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10187 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10188 pvInfoList[currentMove].depth = programStats.depth;
10189 pvInfoList[currentMove].score = programStats.score;
10190 pvInfoList[currentMove].time = 0;
10191 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10194 if (currentMove >= forwardMostMove) {
10195 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10196 gameMode = EditGame;
10199 /* [AS] Clear current move marker at the end of a game */
10200 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10205 toX = moveList[currentMove][2] - AAA;
10206 toY = moveList[currentMove][3] - ONE;
10208 if (moveList[currentMove][1] == '@') {
10209 if (appData.highlightLastMove) {
10210 SetHighlights(-1, -1, toX, toY);
10213 fromX = moveList[currentMove][0] - AAA;
10214 fromY = moveList[currentMove][1] - ONE;
10216 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10218 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10220 if (appData.highlightLastMove) {
10221 SetHighlights(fromX, fromY, toX, toY);
10224 DisplayMove(currentMove);
10225 SendMoveToProgram(currentMove++, &first);
10226 DisplayBothClocks();
10227 DrawPosition(FALSE, boards[currentMove]);
10228 // [HGM] PV info: always display, routine tests if empty
10229 DisplayComment(currentMove - 1, commentList[currentMove]);
10235 LoadGameOneMove(readAhead)
10236 ChessMove readAhead;
10238 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10239 char promoChar = NULLCHAR;
10240 ChessMove moveType;
10241 char move[MSG_SIZ];
10244 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10245 gameMode != AnalyzeMode && gameMode != Training) {
10250 yyboardindex = forwardMostMove;
10251 if (readAhead != EndOfFile) {
10252 moveType = readAhead;
10254 if (gameFileFP == NULL)
10256 moveType = (ChessMove) Myylex();
10260 switch (moveType) {
10262 if (appData.debugMode)
10263 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10266 /* append the comment but don't display it */
10267 AppendComment(currentMove, p, FALSE);
10270 case WhiteCapturesEnPassant:
10271 case BlackCapturesEnPassant:
10272 case WhitePromotion:
10273 case BlackPromotion:
10274 case WhiteNonPromotion:
10275 case BlackNonPromotion:
10277 case WhiteKingSideCastle:
10278 case WhiteQueenSideCastle:
10279 case BlackKingSideCastle:
10280 case BlackQueenSideCastle:
10281 case WhiteKingSideCastleWild:
10282 case WhiteQueenSideCastleWild:
10283 case BlackKingSideCastleWild:
10284 case BlackQueenSideCastleWild:
10286 case WhiteHSideCastleFR:
10287 case WhiteASideCastleFR:
10288 case BlackHSideCastleFR:
10289 case BlackASideCastleFR:
10291 if (appData.debugMode)
10292 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10293 fromX = currentMoveString[0] - AAA;
10294 fromY = currentMoveString[1] - ONE;
10295 toX = currentMoveString[2] - AAA;
10296 toY = currentMoveString[3] - ONE;
10297 promoChar = currentMoveString[4];
10302 if (appData.debugMode)
10303 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10304 fromX = moveType == WhiteDrop ?
10305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10306 (int) CharToPiece(ToLower(currentMoveString[0]));
10308 toX = currentMoveString[2] - AAA;
10309 toY = currentMoveString[3] - ONE;
10315 case GameUnfinished:
10316 if (appData.debugMode)
10317 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10318 p = strchr(yy_text, '{');
10319 if (p == NULL) p = strchr(yy_text, '(');
10322 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10324 q = strchr(p, *p == '{' ? '}' : ')');
10325 if (q != NULL) *q = NULLCHAR;
10328 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10329 GameEnds(moveType, p, GE_FILE);
10331 if (cmailMsgLoaded) {
10333 flipView = WhiteOnMove(currentMove);
10334 if (moveType == GameUnfinished) flipView = !flipView;
10335 if (appData.debugMode)
10336 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10341 if (appData.debugMode)
10342 fprintf(debugFP, "Parser hit end of file\n");
10343 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10349 if (WhiteOnMove(currentMove)) {
10350 GameEnds(BlackWins, "Black mates", GE_FILE);
10352 GameEnds(WhiteWins, "White mates", GE_FILE);
10356 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10362 case MoveNumberOne:
10363 if (lastLoadGameStart == GNUChessGame) {
10364 /* GNUChessGames have numbers, but they aren't move numbers */
10365 if (appData.debugMode)
10366 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10367 yy_text, (int) moveType);
10368 return LoadGameOneMove(EndOfFile); /* tail recursion */
10370 /* else fall thru */
10375 /* Reached start of next game in file */
10376 if (appData.debugMode)
10377 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10378 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10384 if (WhiteOnMove(currentMove)) {
10385 GameEnds(BlackWins, "Black mates", GE_FILE);
10387 GameEnds(WhiteWins, "White mates", GE_FILE);
10391 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10397 case PositionDiagram: /* should not happen; ignore */
10398 case ElapsedTime: /* ignore */
10399 case NAG: /* ignore */
10400 if (appData.debugMode)
10401 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10402 yy_text, (int) moveType);
10403 return LoadGameOneMove(EndOfFile); /* tail recursion */
10406 if (appData.testLegality) {
10407 if (appData.debugMode)
10408 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10409 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10410 (forwardMostMove / 2) + 1,
10411 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10412 DisplayError(move, 0);
10415 if (appData.debugMode)
10416 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10417 yy_text, currentMoveString);
10418 fromX = currentMoveString[0] - AAA;
10419 fromY = currentMoveString[1] - ONE;
10420 toX = currentMoveString[2] - AAA;
10421 toY = currentMoveString[3] - ONE;
10422 promoChar = currentMoveString[4];
10426 case AmbiguousMove:
10427 if (appData.debugMode)
10428 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10429 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10430 (forwardMostMove / 2) + 1,
10431 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10432 DisplayError(move, 0);
10437 case ImpossibleMove:
10438 if (appData.debugMode)
10439 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10440 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10441 (forwardMostMove / 2) + 1,
10442 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10443 DisplayError(move, 0);
10449 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10450 DrawPosition(FALSE, boards[currentMove]);
10451 DisplayBothClocks();
10452 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10453 DisplayComment(currentMove - 1, commentList[currentMove]);
10455 (void) StopLoadGameTimer();
10457 cmailOldMove = forwardMostMove;
10460 /* currentMoveString is set as a side-effect of yylex */
10462 thinkOutput[0] = NULLCHAR;
10463 MakeMove(fromX, fromY, toX, toY, promoChar);
10464 currentMove = forwardMostMove;
10469 /* Load the nth game from the given file */
10471 LoadGameFromFile(filename, n, title, useList)
10475 /*Boolean*/ int useList;
10480 if (strcmp(filename, "-") == 0) {
10484 f = fopen(filename, "rb");
10486 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10487 DisplayError(buf, errno);
10491 if (fseek(f, 0, 0) == -1) {
10492 /* f is not seekable; probably a pipe */
10495 if (useList && n == 0) {
10496 int error = GameListBuild(f);
10498 DisplayError(_("Cannot build game list"), error);
10499 } else if (!ListEmpty(&gameList) &&
10500 ((ListGame *) gameList.tailPred)->number > 1) {
10501 GameListPopUp(f, title);
10508 return LoadGame(f, n, title, FALSE);
10513 MakeRegisteredMove()
10515 int fromX, fromY, toX, toY;
10517 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10518 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10521 if (appData.debugMode)
10522 fprintf(debugFP, "Restoring %s for game %d\n",
10523 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10525 thinkOutput[0] = NULLCHAR;
10526 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10527 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10528 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10529 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10530 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10531 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10532 MakeMove(fromX, fromY, toX, toY, promoChar);
10533 ShowMove(fromX, fromY, toX, toY);
10535 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10542 if (WhiteOnMove(currentMove)) {
10543 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10545 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10550 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10557 if (WhiteOnMove(currentMove)) {
10558 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10560 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10565 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10576 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10578 CmailLoadGame(f, gameNumber, title, useList)
10586 if (gameNumber > nCmailGames) {
10587 DisplayError(_("No more games in this message"), 0);
10590 if (f == lastLoadGameFP) {
10591 int offset = gameNumber - lastLoadGameNumber;
10593 cmailMsg[0] = NULLCHAR;
10594 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10595 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10596 nCmailMovesRegistered--;
10598 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10599 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10600 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10603 if (! RegisterMove()) return FALSE;
10607 retVal = LoadGame(f, gameNumber, title, useList);
10609 /* Make move registered during previous look at this game, if any */
10610 MakeRegisteredMove();
10612 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10613 commentList[currentMove]
10614 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10615 DisplayComment(currentMove - 1, commentList[currentMove]);
10621 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10626 int gameNumber = lastLoadGameNumber + offset;
10627 if (lastLoadGameFP == NULL) {
10628 DisplayError(_("No game has been loaded yet"), 0);
10631 if (gameNumber <= 0) {
10632 DisplayError(_("Can't back up any further"), 0);
10635 if (cmailMsgLoaded) {
10636 return CmailLoadGame(lastLoadGameFP, gameNumber,
10637 lastLoadGameTitle, lastLoadGameUseList);
10639 return LoadGame(lastLoadGameFP, gameNumber,
10640 lastLoadGameTitle, lastLoadGameUseList);
10646 /* Load the nth game from open file f */
10648 LoadGame(f, gameNumber, title, useList)
10656 int gn = gameNumber;
10657 ListGame *lg = NULL;
10658 int numPGNTags = 0;
10660 GameMode oldGameMode;
10661 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10663 if (appData.debugMode)
10664 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10666 if (gameMode == Training )
10667 SetTrainingModeOff();
10669 oldGameMode = gameMode;
10670 if (gameMode != BeginningOfGame) {
10671 Reset(FALSE, TRUE);
10675 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10676 fclose(lastLoadGameFP);
10680 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10683 fseek(f, lg->offset, 0);
10684 GameListHighlight(gameNumber);
10688 DisplayError(_("Game number out of range"), 0);
10693 if (fseek(f, 0, 0) == -1) {
10694 if (f == lastLoadGameFP ?
10695 gameNumber == lastLoadGameNumber + 1 :
10699 DisplayError(_("Can't seek on game file"), 0);
10704 lastLoadGameFP = f;
10705 lastLoadGameNumber = gameNumber;
10706 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10707 lastLoadGameUseList = useList;
10711 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10712 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10713 lg->gameInfo.black);
10715 } else if (*title != NULLCHAR) {
10716 if (gameNumber > 1) {
10717 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10720 DisplayTitle(title);
10724 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10725 gameMode = PlayFromGameFile;
10729 currentMove = forwardMostMove = backwardMostMove = 0;
10730 CopyBoard(boards[0], initialPosition);
10734 * Skip the first gn-1 games in the file.
10735 * Also skip over anything that precedes an identifiable
10736 * start of game marker, to avoid being confused by
10737 * garbage at the start of the file. Currently
10738 * recognized start of game markers are the move number "1",
10739 * the pattern "gnuchess .* game", the pattern
10740 * "^[#;%] [^ ]* game file", and a PGN tag block.
10741 * A game that starts with one of the latter two patterns
10742 * will also have a move number 1, possibly
10743 * following a position diagram.
10744 * 5-4-02: Let's try being more lenient and allowing a game to
10745 * start with an unnumbered move. Does that break anything?
10747 cm = lastLoadGameStart = EndOfFile;
10749 yyboardindex = forwardMostMove;
10750 cm = (ChessMove) Myylex();
10753 if (cmailMsgLoaded) {
10754 nCmailGames = CMAIL_MAX_GAMES - gn;
10757 DisplayError(_("Game not found in file"), 0);
10764 lastLoadGameStart = cm;
10767 case MoveNumberOne:
10768 switch (lastLoadGameStart) {
10773 case MoveNumberOne:
10775 gn--; /* count this game */
10776 lastLoadGameStart = cm;
10785 switch (lastLoadGameStart) {
10788 case MoveNumberOne:
10790 gn--; /* count this game */
10791 lastLoadGameStart = cm;
10794 lastLoadGameStart = cm; /* game counted already */
10802 yyboardindex = forwardMostMove;
10803 cm = (ChessMove) Myylex();
10804 } while (cm == PGNTag || cm == Comment);
10811 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10812 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10813 != CMAIL_OLD_RESULT) {
10815 cmailResult[ CMAIL_MAX_GAMES
10816 - gn - 1] = CMAIL_OLD_RESULT;
10822 /* Only a NormalMove can be at the start of a game
10823 * without a position diagram. */
10824 if (lastLoadGameStart == EndOfFile ) {
10826 lastLoadGameStart = MoveNumberOne;
10835 if (appData.debugMode)
10836 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10838 if (cm == XBoardGame) {
10839 /* Skip any header junk before position diagram and/or move 1 */
10841 yyboardindex = forwardMostMove;
10842 cm = (ChessMove) Myylex();
10844 if (cm == EndOfFile ||
10845 cm == GNUChessGame || cm == XBoardGame) {
10846 /* Empty game; pretend end-of-file and handle later */
10851 if (cm == MoveNumberOne || cm == PositionDiagram ||
10852 cm == PGNTag || cm == Comment)
10855 } else if (cm == GNUChessGame) {
10856 if (gameInfo.event != NULL) {
10857 free(gameInfo.event);
10859 gameInfo.event = StrSave(yy_text);
10862 startedFromSetupPosition = FALSE;
10863 while (cm == PGNTag) {
10864 if (appData.debugMode)
10865 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10866 err = ParsePGNTag(yy_text, &gameInfo);
10867 if (!err) numPGNTags++;
10869 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10870 if(gameInfo.variant != oldVariant) {
10871 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10872 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10873 InitPosition(TRUE);
10874 oldVariant = gameInfo.variant;
10875 if (appData.debugMode)
10876 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10880 if (gameInfo.fen != NULL) {
10881 Board initial_position;
10882 startedFromSetupPosition = TRUE;
10883 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10885 DisplayError(_("Bad FEN position in file"), 0);
10888 CopyBoard(boards[0], initial_position);
10889 if (blackPlaysFirst) {
10890 currentMove = forwardMostMove = backwardMostMove = 1;
10891 CopyBoard(boards[1], initial_position);
10892 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10893 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10894 timeRemaining[0][1] = whiteTimeRemaining;
10895 timeRemaining[1][1] = blackTimeRemaining;
10896 if (commentList[0] != NULL) {
10897 commentList[1] = commentList[0];
10898 commentList[0] = NULL;
10901 currentMove = forwardMostMove = backwardMostMove = 0;
10903 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10905 initialRulePlies = FENrulePlies;
10906 for( i=0; i< nrCastlingRights; i++ )
10907 initialRights[i] = initial_position[CASTLING][i];
10909 yyboardindex = forwardMostMove;
10910 free(gameInfo.fen);
10911 gameInfo.fen = NULL;
10914 yyboardindex = forwardMostMove;
10915 cm = (ChessMove) Myylex();
10917 /* Handle comments interspersed among the tags */
10918 while (cm == Comment) {
10920 if (appData.debugMode)
10921 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10923 AppendComment(currentMove, p, FALSE);
10924 yyboardindex = forwardMostMove;
10925 cm = (ChessMove) Myylex();
10929 /* don't rely on existence of Event tag since if game was
10930 * pasted from clipboard the Event tag may not exist
10932 if (numPGNTags > 0){
10934 if (gameInfo.variant == VariantNormal) {
10935 VariantClass v = StringToVariant(gameInfo.event);
10936 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10937 if(v < VariantShogi) gameInfo.variant = v;
10940 if( appData.autoDisplayTags ) {
10941 tags = PGNTags(&gameInfo);
10942 TagsPopUp(tags, CmailMsg());
10947 /* Make something up, but don't display it now */
10952 if (cm == PositionDiagram) {
10955 Board initial_position;
10957 if (appData.debugMode)
10958 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10960 if (!startedFromSetupPosition) {
10962 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10963 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10974 initial_position[i][j++] = CharToPiece(*p);
10977 while (*p == ' ' || *p == '\t' ||
10978 *p == '\n' || *p == '\r') p++;
10980 if (strncmp(p, "black", strlen("black"))==0)
10981 blackPlaysFirst = TRUE;
10983 blackPlaysFirst = FALSE;
10984 startedFromSetupPosition = TRUE;
10986 CopyBoard(boards[0], initial_position);
10987 if (blackPlaysFirst) {
10988 currentMove = forwardMostMove = backwardMostMove = 1;
10989 CopyBoard(boards[1], initial_position);
10990 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10991 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10992 timeRemaining[0][1] = whiteTimeRemaining;
10993 timeRemaining[1][1] = blackTimeRemaining;
10994 if (commentList[0] != NULL) {
10995 commentList[1] = commentList[0];
10996 commentList[0] = NULL;
10999 currentMove = forwardMostMove = backwardMostMove = 0;
11002 yyboardindex = forwardMostMove;
11003 cm = (ChessMove) Myylex();
11006 if (first.pr == NoProc) {
11007 StartChessProgram(&first);
11009 InitChessProgram(&first, FALSE);
11010 SendToProgram("force\n", &first);
11011 if (startedFromSetupPosition) {
11012 SendBoard(&first, forwardMostMove);
11013 if (appData.debugMode) {
11014 fprintf(debugFP, "Load Game\n");
11016 DisplayBothClocks();
11019 /* [HGM] server: flag to write setup moves in broadcast file as one */
11020 loadFlag = appData.suppressLoadMoves;
11022 while (cm == Comment) {
11024 if (appData.debugMode)
11025 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11027 AppendComment(currentMove, p, FALSE);
11028 yyboardindex = forwardMostMove;
11029 cm = (ChessMove) Myylex();
11032 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11033 cm == WhiteWins || cm == BlackWins ||
11034 cm == GameIsDrawn || cm == GameUnfinished) {
11035 DisplayMessage("", _("No moves in game"));
11036 if (cmailMsgLoaded) {
11037 if (appData.debugMode)
11038 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11042 DrawPosition(FALSE, boards[currentMove]);
11043 DisplayBothClocks();
11044 gameMode = EditGame;
11051 // [HGM] PV info: routine tests if comment empty
11052 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11053 DisplayComment(currentMove - 1, commentList[currentMove]);
11055 if (!matchMode && appData.timeDelay != 0)
11056 DrawPosition(FALSE, boards[currentMove]);
11058 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11059 programStats.ok_to_send = 1;
11062 /* if the first token after the PGN tags is a move
11063 * and not move number 1, retrieve it from the parser
11065 if (cm != MoveNumberOne)
11066 LoadGameOneMove(cm);
11068 /* load the remaining moves from the file */
11069 while (LoadGameOneMove(EndOfFile)) {
11070 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11071 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11074 /* rewind to the start of the game */
11075 currentMove = backwardMostMove;
11077 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11079 if (oldGameMode == AnalyzeFile ||
11080 oldGameMode == AnalyzeMode) {
11081 AnalyzeFileEvent();
11084 if (matchMode || appData.timeDelay == 0) {
11086 gameMode = EditGame;
11088 } else if (appData.timeDelay > 0) {
11089 AutoPlayGameLoop();
11092 if (appData.debugMode)
11093 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11095 loadFlag = 0; /* [HGM] true game starts */
11099 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11101 ReloadPosition(offset)
11104 int positionNumber = lastLoadPositionNumber + offset;
11105 if (lastLoadPositionFP == NULL) {
11106 DisplayError(_("No position has been loaded yet"), 0);
11109 if (positionNumber <= 0) {
11110 DisplayError(_("Can't back up any further"), 0);
11113 return LoadPosition(lastLoadPositionFP, positionNumber,
11114 lastLoadPositionTitle);
11117 /* Load the nth position from the given file */
11119 LoadPositionFromFile(filename, n, title)
11127 if (strcmp(filename, "-") == 0) {
11128 return LoadPosition(stdin, n, "stdin");
11130 f = fopen(filename, "rb");
11132 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11133 DisplayError(buf, errno);
11136 return LoadPosition(f, n, title);
11141 /* Load the nth position from the given open file, and close it */
11143 LoadPosition(f, positionNumber, title)
11145 int positionNumber;
11148 char *p, line[MSG_SIZ];
11149 Board initial_position;
11150 int i, j, fenMode, pn;
11152 if (gameMode == Training )
11153 SetTrainingModeOff();
11155 if (gameMode != BeginningOfGame) {
11156 Reset(FALSE, TRUE);
11158 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11159 fclose(lastLoadPositionFP);
11161 if (positionNumber == 0) positionNumber = 1;
11162 lastLoadPositionFP = f;
11163 lastLoadPositionNumber = positionNumber;
11164 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11165 if (first.pr == NoProc) {
11166 StartChessProgram(&first);
11167 InitChessProgram(&first, FALSE);
11169 pn = positionNumber;
11170 if (positionNumber < 0) {
11171 /* Negative position number means to seek to that byte offset */
11172 if (fseek(f, -positionNumber, 0) == -1) {
11173 DisplayError(_("Can't seek on position file"), 0);
11178 if (fseek(f, 0, 0) == -1) {
11179 if (f == lastLoadPositionFP ?
11180 positionNumber == lastLoadPositionNumber + 1 :
11181 positionNumber == 1) {
11184 DisplayError(_("Can't seek on position file"), 0);
11189 /* See if this file is FEN or old-style xboard */
11190 if (fgets(line, MSG_SIZ, f) == NULL) {
11191 DisplayError(_("Position not found in file"), 0);
11194 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11195 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11198 if (fenMode || line[0] == '#') pn--;
11200 /* skip positions before number pn */
11201 if (fgets(line, MSG_SIZ, f) == NULL) {
11203 DisplayError(_("Position not found in file"), 0);
11206 if (fenMode || line[0] == '#') pn--;
11211 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11212 DisplayError(_("Bad FEN position in file"), 0);
11216 (void) fgets(line, MSG_SIZ, f);
11217 (void) fgets(line, MSG_SIZ, f);
11219 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11220 (void) fgets(line, MSG_SIZ, f);
11221 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11224 initial_position[i][j++] = CharToPiece(*p);
11228 blackPlaysFirst = FALSE;
11230 (void) fgets(line, MSG_SIZ, f);
11231 if (strncmp(line, "black", strlen("black"))==0)
11232 blackPlaysFirst = TRUE;
11235 startedFromSetupPosition = TRUE;
11237 SendToProgram("force\n", &first);
11238 CopyBoard(boards[0], initial_position);
11239 if (blackPlaysFirst) {
11240 currentMove = forwardMostMove = backwardMostMove = 1;
11241 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11242 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11243 CopyBoard(boards[1], initial_position);
11244 DisplayMessage("", _("Black to play"));
11246 currentMove = forwardMostMove = backwardMostMove = 0;
11247 DisplayMessage("", _("White to play"));
11249 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11250 SendBoard(&first, forwardMostMove);
11251 if (appData.debugMode) {
11253 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11254 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11255 fprintf(debugFP, "Load Position\n");
11258 if (positionNumber > 1) {
11259 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11260 DisplayTitle(line);
11262 DisplayTitle(title);
11264 gameMode = EditGame;
11267 timeRemaining[0][1] = whiteTimeRemaining;
11268 timeRemaining[1][1] = blackTimeRemaining;
11269 DrawPosition(FALSE, boards[currentMove]);
11276 CopyPlayerNameIntoFileName(dest, src)
11279 while (*src != NULLCHAR && *src != ',') {
11284 *(*dest)++ = *src++;
11289 char *DefaultFileName(ext)
11292 static char def[MSG_SIZ];
11295 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11297 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11299 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11301 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11308 /* Save the current game to the given file */
11310 SaveGameToFile(filename, append)
11318 if (strcmp(filename, "-") == 0) {
11319 return SaveGame(stdout, 0, NULL);
11321 f = fopen(filename, append ? "a" : "w");
11323 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11324 DisplayError(buf, errno);
11327 safeStrCpy(buf, lastMsg, MSG_SIZ);
11328 DisplayMessage(_("Waiting for access to save file"), "");
11329 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11330 DisplayMessage(_("Saving game"), "");
11331 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11332 result = SaveGame(f, 0, NULL);
11333 DisplayMessage(buf, "");
11343 static char buf[MSG_SIZ];
11346 p = strchr(str, ' ');
11347 if (p == NULL) return str;
11348 strncpy(buf, str, p - str);
11349 buf[p - str] = NULLCHAR;
11353 #define PGN_MAX_LINE 75
11355 #define PGN_SIDE_WHITE 0
11356 #define PGN_SIDE_BLACK 1
11359 static int FindFirstMoveOutOfBook( int side )
11363 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11364 int index = backwardMostMove;
11365 int has_book_hit = 0;
11367 if( (index % 2) != side ) {
11371 while( index < forwardMostMove ) {
11372 /* Check to see if engine is in book */
11373 int depth = pvInfoList[index].depth;
11374 int score = pvInfoList[index].score;
11380 else if( score == 0 && depth == 63 ) {
11381 in_book = 1; /* Zappa */
11383 else if( score == 2 && depth == 99 ) {
11384 in_book = 1; /* Abrok */
11387 has_book_hit += in_book;
11403 void GetOutOfBookInfo( char * buf )
11407 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11409 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11410 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11414 if( oob[0] >= 0 || oob[1] >= 0 ) {
11415 for( i=0; i<2; i++ ) {
11419 if( i > 0 && oob[0] >= 0 ) {
11420 strcat( buf, " " );
11423 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11424 sprintf( buf+strlen(buf), "%s%.2f",
11425 pvInfoList[idx].score >= 0 ? "+" : "",
11426 pvInfoList[idx].score / 100.0 );
11432 /* Save game in PGN style and close the file */
11437 int i, offset, linelen, newblock;
11441 int movelen, numlen, blank;
11442 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11444 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11446 tm = time((time_t *) NULL);
11448 PrintPGNTags(f, &gameInfo);
11450 if (backwardMostMove > 0 || startedFromSetupPosition) {
11451 char *fen = PositionToFEN(backwardMostMove, NULL);
11452 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11453 fprintf(f, "\n{--------------\n");
11454 PrintPosition(f, backwardMostMove);
11455 fprintf(f, "--------------}\n");
11459 /* [AS] Out of book annotation */
11460 if( appData.saveOutOfBookInfo ) {
11463 GetOutOfBookInfo( buf );
11465 if( buf[0] != '\0' ) {
11466 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11473 i = backwardMostMove;
11477 while (i < forwardMostMove) {
11478 /* Print comments preceding this move */
11479 if (commentList[i] != NULL) {
11480 if (linelen > 0) fprintf(f, "\n");
11481 fprintf(f, "%s", commentList[i]);
11486 /* Format move number */
11488 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11491 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11493 numtext[0] = NULLCHAR;
11495 numlen = strlen(numtext);
11498 /* Print move number */
11499 blank = linelen > 0 && numlen > 0;
11500 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11509 fprintf(f, "%s", numtext);
11513 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11514 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11517 blank = linelen > 0 && movelen > 0;
11518 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11527 fprintf(f, "%s", move_buffer);
11528 linelen += movelen;
11530 /* [AS] Add PV info if present */
11531 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11532 /* [HGM] add time */
11533 char buf[MSG_SIZ]; int seconds;
11535 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11541 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11544 seconds = (seconds + 4)/10; // round to full seconds
11546 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11548 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11551 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11552 pvInfoList[i].score >= 0 ? "+" : "",
11553 pvInfoList[i].score / 100.0,
11554 pvInfoList[i].depth,
11557 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11559 /* Print score/depth */
11560 blank = linelen > 0 && movelen > 0;
11561 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11570 fprintf(f, "%s", move_buffer);
11571 linelen += movelen;
11577 /* Start a new line */
11578 if (linelen > 0) fprintf(f, "\n");
11580 /* Print comments after last move */
11581 if (commentList[i] != NULL) {
11582 fprintf(f, "%s\n", commentList[i]);
11586 if (gameInfo.resultDetails != NULL &&
11587 gameInfo.resultDetails[0] != NULLCHAR) {
11588 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11589 PGNResult(gameInfo.result));
11591 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11595 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11599 /* Save game in old style and close the file */
11601 SaveGameOldStyle(f)
11607 tm = time((time_t *) NULL);
11609 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11612 if (backwardMostMove > 0 || startedFromSetupPosition) {
11613 fprintf(f, "\n[--------------\n");
11614 PrintPosition(f, backwardMostMove);
11615 fprintf(f, "--------------]\n");
11620 i = backwardMostMove;
11621 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11623 while (i < forwardMostMove) {
11624 if (commentList[i] != NULL) {
11625 fprintf(f, "[%s]\n", commentList[i]);
11628 if ((i % 2) == 1) {
11629 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11632 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11634 if (commentList[i] != NULL) {
11638 if (i >= forwardMostMove) {
11642 fprintf(f, "%s\n", parseList[i]);
11647 if (commentList[i] != NULL) {
11648 fprintf(f, "[%s]\n", commentList[i]);
11651 /* This isn't really the old style, but it's close enough */
11652 if (gameInfo.resultDetails != NULL &&
11653 gameInfo.resultDetails[0] != NULLCHAR) {
11654 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11655 gameInfo.resultDetails);
11657 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11664 /* Save the current game to open file f and close the file */
11666 SaveGame(f, dummy, dummy2)
11671 if (gameMode == EditPosition) EditPositionDone(TRUE);
11672 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11673 if (appData.oldSaveStyle)
11674 return SaveGameOldStyle(f);
11676 return SaveGamePGN(f);
11679 /* Save the current position to the given file */
11681 SavePositionToFile(filename)
11687 if (strcmp(filename, "-") == 0) {
11688 return SavePosition(stdout, 0, NULL);
11690 f = fopen(filename, "a");
11692 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11693 DisplayError(buf, errno);
11696 safeStrCpy(buf, lastMsg, MSG_SIZ);
11697 DisplayMessage(_("Waiting for access to save file"), "");
11698 flock(fileno(f), LOCK_EX); // [HGM] lock
11699 DisplayMessage(_("Saving position"), "");
11700 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11701 SavePosition(f, 0, NULL);
11702 DisplayMessage(buf, "");
11708 /* Save the current position to the given open file and close the file */
11710 SavePosition(f, dummy, dummy2)
11718 if (gameMode == EditPosition) EditPositionDone(TRUE);
11719 if (appData.oldSaveStyle) {
11720 tm = time((time_t *) NULL);
11722 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11724 fprintf(f, "[--------------\n");
11725 PrintPosition(f, currentMove);
11726 fprintf(f, "--------------]\n");
11728 fen = PositionToFEN(currentMove, NULL);
11729 fprintf(f, "%s\n", fen);
11737 ReloadCmailMsgEvent(unregister)
11741 static char *inFilename = NULL;
11742 static char *outFilename;
11744 struct stat inbuf, outbuf;
11747 /* Any registered moves are unregistered if unregister is set, */
11748 /* i.e. invoked by the signal handler */
11750 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11751 cmailMoveRegistered[i] = FALSE;
11752 if (cmailCommentList[i] != NULL) {
11753 free(cmailCommentList[i]);
11754 cmailCommentList[i] = NULL;
11757 nCmailMovesRegistered = 0;
11760 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11761 cmailResult[i] = CMAIL_NOT_RESULT;
11765 if (inFilename == NULL) {
11766 /* Because the filenames are static they only get malloced once */
11767 /* and they never get freed */
11768 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11769 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11771 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11772 sprintf(outFilename, "%s.out", appData.cmailGameName);
11775 status = stat(outFilename, &outbuf);
11777 cmailMailedMove = FALSE;
11779 status = stat(inFilename, &inbuf);
11780 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11783 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11784 counts the games, notes how each one terminated, etc.
11786 It would be nice to remove this kludge and instead gather all
11787 the information while building the game list. (And to keep it
11788 in the game list nodes instead of having a bunch of fixed-size
11789 parallel arrays.) Note this will require getting each game's
11790 termination from the PGN tags, as the game list builder does
11791 not process the game moves. --mann
11793 cmailMsgLoaded = TRUE;
11794 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11796 /* Load first game in the file or popup game menu */
11797 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11799 #endif /* !WIN32 */
11807 char string[MSG_SIZ];
11809 if ( cmailMailedMove
11810 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11811 return TRUE; /* Allow free viewing */
11814 /* Unregister move to ensure that we don't leave RegisterMove */
11815 /* with the move registered when the conditions for registering no */
11817 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11818 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11819 nCmailMovesRegistered --;
11821 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11823 free(cmailCommentList[lastLoadGameNumber - 1]);
11824 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11828 if (cmailOldMove == -1) {
11829 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11833 if (currentMove > cmailOldMove + 1) {
11834 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11838 if (currentMove < cmailOldMove) {
11839 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11843 if (forwardMostMove > currentMove) {
11844 /* Silently truncate extra moves */
11848 if ( (currentMove == cmailOldMove + 1)
11849 || ( (currentMove == cmailOldMove)
11850 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11851 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11852 if (gameInfo.result != GameUnfinished) {
11853 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11856 if (commentList[currentMove] != NULL) {
11857 cmailCommentList[lastLoadGameNumber - 1]
11858 = StrSave(commentList[currentMove]);
11860 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11862 if (appData.debugMode)
11863 fprintf(debugFP, "Saving %s for game %d\n",
11864 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11866 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11868 f = fopen(string, "w");
11869 if (appData.oldSaveStyle) {
11870 SaveGameOldStyle(f); /* also closes the file */
11872 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11873 f = fopen(string, "w");
11874 SavePosition(f, 0, NULL); /* also closes the file */
11876 fprintf(f, "{--------------\n");
11877 PrintPosition(f, currentMove);
11878 fprintf(f, "--------------}\n\n");
11880 SaveGame(f, 0, NULL); /* also closes the file*/
11883 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11884 nCmailMovesRegistered ++;
11885 } else if (nCmailGames == 1) {
11886 DisplayError(_("You have not made a move yet"), 0);
11897 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11898 FILE *commandOutput;
11899 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11900 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11906 if (! cmailMsgLoaded) {
11907 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11911 if (nCmailGames == nCmailResults) {
11912 DisplayError(_("No unfinished games"), 0);
11916 #if CMAIL_PROHIBIT_REMAIL
11917 if (cmailMailedMove) {
11918 snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
11919 DisplayError(msg, 0);
11924 if (! (cmailMailedMove || RegisterMove())) return;
11926 if ( cmailMailedMove
11927 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11928 snprintf(string, MSG_SIZ, partCommandString,
11929 appData.debugMode ? " -v" : "", appData.cmailGameName);
11930 commandOutput = popen(string, "r");
11932 if (commandOutput == NULL) {
11933 DisplayError(_("Failed to invoke cmail"), 0);
11935 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11936 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11938 if (nBuffers > 1) {
11939 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11940 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11941 nBytes = MSG_SIZ - 1;
11943 (void) memcpy(msg, buffer, nBytes);
11945 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11947 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11948 cmailMailedMove = TRUE; /* Prevent >1 moves */
11951 for (i = 0; i < nCmailGames; i ++) {
11952 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11957 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11959 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11961 appData.cmailGameName,
11963 LoadGameFromFile(buffer, 1, buffer, FALSE);
11964 cmailMsgLoaded = FALSE;
11968 DisplayInformation(msg);
11969 pclose(commandOutput);
11972 if ((*cmailMsg) != '\0') {
11973 DisplayInformation(cmailMsg);
11978 #endif /* !WIN32 */
11987 int prependComma = 0;
11989 char string[MSG_SIZ]; /* Space for game-list */
11992 if (!cmailMsgLoaded) return "";
11994 if (cmailMailedMove) {
11995 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11997 /* Create a list of games left */
11998 snprintf(string, MSG_SIZ, "[");
11999 for (i = 0; i < nCmailGames; i ++) {
12000 if (! ( cmailMoveRegistered[i]
12001 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12002 if (prependComma) {
12003 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12005 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12009 strcat(string, number);
12012 strcat(string, "]");
12014 if (nCmailMovesRegistered + nCmailResults == 0) {
12015 switch (nCmailGames) {
12017 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12021 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12025 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12030 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12032 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12037 if (nCmailResults == nCmailGames) {
12038 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12040 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12045 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12057 if (gameMode == Training)
12058 SetTrainingModeOff();
12061 cmailMsgLoaded = FALSE;
12062 if (appData.icsActive) {
12063 SendToICS(ics_prefix);
12064 SendToICS("refresh\n");
12074 /* Give up on clean exit */
12078 /* Keep trying for clean exit */
12082 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12084 if (telnetISR != NULL) {
12085 RemoveInputSource(telnetISR);
12087 if (icsPR != NoProc) {
12088 DestroyChildProcess(icsPR, TRUE);
12091 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12092 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12094 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12095 /* make sure this other one finishes before killing it! */
12096 if(endingGame) { int count = 0;
12097 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12098 while(endingGame && count++ < 10) DoSleep(1);
12099 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12102 /* Kill off chess programs */
12103 if (first.pr != NoProc) {
12106 DoSleep( appData.delayBeforeQuit );
12107 SendToProgram("quit\n", &first);
12108 DoSleep( appData.delayAfterQuit );
12109 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12111 if (second.pr != NoProc) {
12112 DoSleep( appData.delayBeforeQuit );
12113 SendToProgram("quit\n", &second);
12114 DoSleep( appData.delayAfterQuit );
12115 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12117 if (first.isr != NULL) {
12118 RemoveInputSource(first.isr);
12120 if (second.isr != NULL) {
12121 RemoveInputSource(second.isr);
12124 ShutDownFrontEnd();
12131 if (appData.debugMode)
12132 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12136 if (gameMode == MachinePlaysWhite ||
12137 gameMode == MachinePlaysBlack) {
12140 DisplayBothClocks();
12142 if (gameMode == PlayFromGameFile) {
12143 if (appData.timeDelay >= 0)
12144 AutoPlayGameLoop();
12145 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12146 Reset(FALSE, TRUE);
12147 SendToICS(ics_prefix);
12148 SendToICS("refresh\n");
12149 } else if (currentMove < forwardMostMove) {
12150 ForwardInner(forwardMostMove);
12152 pauseExamInvalid = FALSE;
12154 switch (gameMode) {
12158 pauseExamForwardMostMove = forwardMostMove;
12159 pauseExamInvalid = FALSE;
12162 case IcsPlayingWhite:
12163 case IcsPlayingBlack:
12167 case PlayFromGameFile:
12168 (void) StopLoadGameTimer();
12172 case BeginningOfGame:
12173 if (appData.icsActive) return;
12174 /* else fall through */
12175 case MachinePlaysWhite:
12176 case MachinePlaysBlack:
12177 case TwoMachinesPlay:
12178 if (forwardMostMove == 0)
12179 return; /* don't pause if no one has moved */
12180 if ((gameMode == MachinePlaysWhite &&
12181 !WhiteOnMove(forwardMostMove)) ||
12182 (gameMode == MachinePlaysBlack &&
12183 WhiteOnMove(forwardMostMove))) {
12196 char title[MSG_SIZ];
12198 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12199 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12201 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12202 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12203 parseList[currentMove - 1]);
12206 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12213 char *tags = PGNTags(&gameInfo);
12214 EditTagsPopUp(tags, NULL);
12221 if (appData.noChessProgram || gameMode == AnalyzeMode)
12224 if (gameMode != AnalyzeFile) {
12225 if (!appData.icsEngineAnalyze) {
12227 if (gameMode != EditGame) return;
12229 ResurrectChessProgram();
12230 SendToProgram("analyze\n", &first);
12231 first.analyzing = TRUE;
12232 /*first.maybeThinking = TRUE;*/
12233 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12234 EngineOutputPopUp();
12236 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12241 StartAnalysisClock();
12242 GetTimeMark(&lastNodeCountTime);
12249 if (appData.noChessProgram || gameMode == AnalyzeFile)
12252 if (gameMode != AnalyzeMode) {
12254 if (gameMode != EditGame) return;
12255 ResurrectChessProgram();
12256 SendToProgram("analyze\n", &first);
12257 first.analyzing = TRUE;
12258 /*first.maybeThinking = TRUE;*/
12259 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12260 EngineOutputPopUp();
12262 gameMode = AnalyzeFile;
12267 StartAnalysisClock();
12268 GetTimeMark(&lastNodeCountTime);
12273 MachineWhiteEvent()
12276 char *bookHit = NULL;
12278 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12282 if (gameMode == PlayFromGameFile ||
12283 gameMode == TwoMachinesPlay ||
12284 gameMode == Training ||
12285 gameMode == AnalyzeMode ||
12286 gameMode == EndOfGame)
12289 if (gameMode == EditPosition)
12290 EditPositionDone(TRUE);
12292 if (!WhiteOnMove(currentMove)) {
12293 DisplayError(_("It is not White's turn"), 0);
12297 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12300 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12301 gameMode == AnalyzeFile)
12304 ResurrectChessProgram(); /* in case it isn't running */
12305 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12306 gameMode = MachinePlaysWhite;
12309 gameMode = MachinePlaysWhite;
12313 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12315 if (first.sendName) {
12316 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12317 SendToProgram(buf, &first);
12319 if (first.sendTime) {
12320 if (first.useColors) {
12321 SendToProgram("black\n", &first); /*gnu kludge*/
12323 SendTimeRemaining(&first, TRUE);
12325 if (first.useColors) {
12326 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12328 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12329 SetMachineThinkingEnables();
12330 first.maybeThinking = TRUE;
12334 if (appData.autoFlipView && !flipView) {
12335 flipView = !flipView;
12336 DrawPosition(FALSE, NULL);
12337 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12340 if(bookHit) { // [HGM] book: simulate book reply
12341 static char bookMove[MSG_SIZ]; // a bit generous?
12343 programStats.nodes = programStats.depth = programStats.time =
12344 programStats.score = programStats.got_only_move = 0;
12345 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12347 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12348 strcat(bookMove, bookHit);
12349 HandleMachineMove(bookMove, &first);
12354 MachineBlackEvent()
12357 char *bookHit = NULL;
12359 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12363 if (gameMode == PlayFromGameFile ||
12364 gameMode == TwoMachinesPlay ||
12365 gameMode == Training ||
12366 gameMode == AnalyzeMode ||
12367 gameMode == EndOfGame)
12370 if (gameMode == EditPosition)
12371 EditPositionDone(TRUE);
12373 if (WhiteOnMove(currentMove)) {
12374 DisplayError(_("It is not Black's turn"), 0);
12378 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12381 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12382 gameMode == AnalyzeFile)
12385 ResurrectChessProgram(); /* in case it isn't running */
12386 gameMode = MachinePlaysBlack;
12390 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12392 if (first.sendName) {
12393 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12394 SendToProgram(buf, &first);
12396 if (first.sendTime) {
12397 if (first.useColors) {
12398 SendToProgram("white\n", &first); /*gnu kludge*/
12400 SendTimeRemaining(&first, FALSE);
12402 if (first.useColors) {
12403 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12405 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12406 SetMachineThinkingEnables();
12407 first.maybeThinking = TRUE;
12410 if (appData.autoFlipView && flipView) {
12411 flipView = !flipView;
12412 DrawPosition(FALSE, NULL);
12413 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12415 if(bookHit) { // [HGM] book: simulate book reply
12416 static char bookMove[MSG_SIZ]; // a bit generous?
12418 programStats.nodes = programStats.depth = programStats.time =
12419 programStats.score = programStats.got_only_move = 0;
12420 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12422 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12423 strcat(bookMove, bookHit);
12424 HandleMachineMove(bookMove, &first);
12430 DisplayTwoMachinesTitle()
12433 if (appData.matchGames > 0) {
12434 if (first.twoMachinesColor[0] == 'w') {
12435 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12436 gameInfo.white, gameInfo.black,
12437 first.matchWins, second.matchWins,
12438 matchGame - 1 - (first.matchWins + second.matchWins));
12440 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12441 gameInfo.white, gameInfo.black,
12442 second.matchWins, first.matchWins,
12443 matchGame - 1 - (first.matchWins + second.matchWins));
12446 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12452 SettingsMenuIfReady()
12454 if (second.lastPing != second.lastPong) {
12455 DisplayMessage("", _("Waiting for second chess program"));
12456 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12460 DisplayMessage("", "");
12461 SettingsPopUp(&second);
12465 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12468 if (cps->pr == NULL) {
12469 StartChessProgram(cps);
12470 if (cps->protocolVersion == 1) {
12473 /* kludge: allow timeout for initial "feature" command */
12475 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12476 DisplayMessage("", buf);
12477 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12485 TwoMachinesEvent P((void))
12489 ChessProgramState *onmove;
12490 char *bookHit = NULL;
12491 static int stalling = 0;
12495 if (appData.noChessProgram) return;
12497 switch (gameMode) {
12498 case TwoMachinesPlay:
12500 case MachinePlaysWhite:
12501 case MachinePlaysBlack:
12502 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12503 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12507 case BeginningOfGame:
12508 case PlayFromGameFile:
12511 if (gameMode != EditGame) return;
12514 EditPositionDone(TRUE);
12525 // forwardMostMove = currentMove;
12526 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12528 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12530 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12531 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12532 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12536 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12537 SendToProgram("force\n", &second);
12539 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12542 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12543 if(appData.matchPause>10000 || appData.matchPause<10)
12544 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12545 wait = SubtractTimeMarks(&now, &pauseStart);
12546 if(wait < appData.matchPause) {
12547 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12551 DisplayMessage("", "");
12552 if (startedFromSetupPosition) {
12553 SendBoard(&second, backwardMostMove);
12554 if (appData.debugMode) {
12555 fprintf(debugFP, "Two Machines\n");
12558 for (i = backwardMostMove; i < forwardMostMove; i++) {
12559 SendMoveToProgram(i, &second);
12562 gameMode = TwoMachinesPlay;
12566 DisplayTwoMachinesTitle();
12568 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12573 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12574 SendToProgram(first.computerString, &first);
12575 if (first.sendName) {
12576 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12577 SendToProgram(buf, &first);
12579 SendToProgram(second.computerString, &second);
12580 if (second.sendName) {
12581 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12582 SendToProgram(buf, &second);
12586 if (!first.sendTime || !second.sendTime) {
12587 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12588 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12590 if (onmove->sendTime) {
12591 if (onmove->useColors) {
12592 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12594 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12596 if (onmove->useColors) {
12597 SendToProgram(onmove->twoMachinesColor, onmove);
12599 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12600 // SendToProgram("go\n", onmove);
12601 onmove->maybeThinking = TRUE;
12602 SetMachineThinkingEnables();
12606 if(bookHit) { // [HGM] book: simulate book reply
12607 static char bookMove[MSG_SIZ]; // a bit generous?
12609 programStats.nodes = programStats.depth = programStats.time =
12610 programStats.score = programStats.got_only_move = 0;
12611 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12613 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12614 strcat(bookMove, bookHit);
12615 savedMessage = bookMove; // args for deferred call
12616 savedState = onmove;
12617 ScheduleDelayedEvent(DeferredBookMove, 1);
12624 if (gameMode == Training) {
12625 SetTrainingModeOff();
12626 gameMode = PlayFromGameFile;
12627 DisplayMessage("", _("Training mode off"));
12629 gameMode = Training;
12630 animateTraining = appData.animate;
12632 /* make sure we are not already at the end of the game */
12633 if (currentMove < forwardMostMove) {
12634 SetTrainingModeOn();
12635 DisplayMessage("", _("Training mode on"));
12637 gameMode = PlayFromGameFile;
12638 DisplayError(_("Already at end of game"), 0);
12647 if (!appData.icsActive) return;
12648 switch (gameMode) {
12649 case IcsPlayingWhite:
12650 case IcsPlayingBlack:
12653 case BeginningOfGame:
12661 EditPositionDone(TRUE);
12674 gameMode = IcsIdle;
12685 switch (gameMode) {
12687 SetTrainingModeOff();
12689 case MachinePlaysWhite:
12690 case MachinePlaysBlack:
12691 case BeginningOfGame:
12692 SendToProgram("force\n", &first);
12693 SetUserThinkingEnables();
12695 case PlayFromGameFile:
12696 (void) StopLoadGameTimer();
12697 if (gameFileFP != NULL) {
12702 EditPositionDone(TRUE);
12707 SendToProgram("force\n", &first);
12709 case TwoMachinesPlay:
12710 GameEnds(EndOfFile, NULL, GE_PLAYER);
12711 ResurrectChessProgram();
12712 SetUserThinkingEnables();
12715 ResurrectChessProgram();
12717 case IcsPlayingBlack:
12718 case IcsPlayingWhite:
12719 DisplayError(_("Warning: You are still playing a game"), 0);
12722 DisplayError(_("Warning: You are still observing a game"), 0);
12725 DisplayError(_("Warning: You are still examining a game"), 0);
12736 first.offeredDraw = second.offeredDraw = 0;
12738 if (gameMode == PlayFromGameFile) {
12739 whiteTimeRemaining = timeRemaining[0][currentMove];
12740 blackTimeRemaining = timeRemaining[1][currentMove];
12744 if (gameMode == MachinePlaysWhite ||
12745 gameMode == MachinePlaysBlack ||
12746 gameMode == TwoMachinesPlay ||
12747 gameMode == EndOfGame) {
12748 i = forwardMostMove;
12749 while (i > currentMove) {
12750 SendToProgram("undo\n", &first);
12753 whiteTimeRemaining = timeRemaining[0][currentMove];
12754 blackTimeRemaining = timeRemaining[1][currentMove];
12755 DisplayBothClocks();
12756 if (whiteFlag || blackFlag) {
12757 whiteFlag = blackFlag = 0;
12762 gameMode = EditGame;
12769 EditPositionEvent()
12771 if (gameMode == EditPosition) {
12777 if (gameMode != EditGame) return;
12779 gameMode = EditPosition;
12782 if (currentMove > 0)
12783 CopyBoard(boards[0], boards[currentMove]);
12785 blackPlaysFirst = !WhiteOnMove(currentMove);
12787 currentMove = forwardMostMove = backwardMostMove = 0;
12788 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12795 /* [DM] icsEngineAnalyze - possible call from other functions */
12796 if (appData.icsEngineAnalyze) {
12797 appData.icsEngineAnalyze = FALSE;
12799 DisplayMessage("",_("Close ICS engine analyze..."));
12801 if (first.analysisSupport && first.analyzing) {
12802 SendToProgram("exit\n", &first);
12803 first.analyzing = FALSE;
12805 thinkOutput[0] = NULLCHAR;
12809 EditPositionDone(Boolean fakeRights)
12811 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12813 startedFromSetupPosition = TRUE;
12814 InitChessProgram(&first, FALSE);
12815 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12816 boards[0][EP_STATUS] = EP_NONE;
12817 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12818 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12819 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12820 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12821 } else boards[0][CASTLING][2] = NoRights;
12822 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12823 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12824 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12825 } else boards[0][CASTLING][5] = NoRights;
12827 SendToProgram("force\n", &first);
12828 if (blackPlaysFirst) {
12829 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12830 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12831 currentMove = forwardMostMove = backwardMostMove = 1;
12832 CopyBoard(boards[1], boards[0]);
12834 currentMove = forwardMostMove = backwardMostMove = 0;
12836 SendBoard(&first, forwardMostMove);
12837 if (appData.debugMode) {
12838 fprintf(debugFP, "EditPosDone\n");
12841 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12842 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12843 gameMode = EditGame;
12845 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12846 ClearHighlights(); /* [AS] */
12849 /* Pause for `ms' milliseconds */
12850 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12860 } while (SubtractTimeMarks(&m2, &m1) < ms);
12863 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12865 SendMultiLineToICS(buf)
12868 char temp[MSG_SIZ+1], *p;
12875 strncpy(temp, buf, len);
12880 if (*p == '\n' || *p == '\r')
12885 strcat(temp, "\n");
12887 SendToPlayer(temp, strlen(temp));
12891 SetWhiteToPlayEvent()
12893 if (gameMode == EditPosition) {
12894 blackPlaysFirst = FALSE;
12895 DisplayBothClocks(); /* works because currentMove is 0 */
12896 } else if (gameMode == IcsExamining) {
12897 SendToICS(ics_prefix);
12898 SendToICS("tomove white\n");
12903 SetBlackToPlayEvent()
12905 if (gameMode == EditPosition) {
12906 blackPlaysFirst = TRUE;
12907 currentMove = 1; /* kludge */
12908 DisplayBothClocks();
12910 } else if (gameMode == IcsExamining) {
12911 SendToICS(ics_prefix);
12912 SendToICS("tomove black\n");
12917 EditPositionMenuEvent(selection, x, y)
12918 ChessSquare selection;
12922 ChessSquare piece = boards[0][y][x];
12924 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12926 switch (selection) {
12928 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12929 SendToICS(ics_prefix);
12930 SendToICS("bsetup clear\n");
12931 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12932 SendToICS(ics_prefix);
12933 SendToICS("clearboard\n");
12935 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12936 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12937 for (y = 0; y < BOARD_HEIGHT; y++) {
12938 if (gameMode == IcsExamining) {
12939 if (boards[currentMove][y][x] != EmptySquare) {
12940 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12945 boards[0][y][x] = p;
12950 if (gameMode == EditPosition) {
12951 DrawPosition(FALSE, boards[0]);
12956 SetWhiteToPlayEvent();
12960 SetBlackToPlayEvent();
12964 if (gameMode == IcsExamining) {
12965 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12966 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12969 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12970 if(x == BOARD_LEFT-2) {
12971 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12972 boards[0][y][1] = 0;
12974 if(x == BOARD_RGHT+1) {
12975 if(y >= gameInfo.holdingsSize) break;
12976 boards[0][y][BOARD_WIDTH-2] = 0;
12979 boards[0][y][x] = EmptySquare;
12980 DrawPosition(FALSE, boards[0]);
12985 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12986 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12987 selection = (ChessSquare) (PROMOTED piece);
12988 } else if(piece == EmptySquare) selection = WhiteSilver;
12989 else selection = (ChessSquare)((int)piece - 1);
12993 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12994 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12995 selection = (ChessSquare) (DEMOTED piece);
12996 } else if(piece == EmptySquare) selection = BlackSilver;
12997 else selection = (ChessSquare)((int)piece + 1);
13002 if(gameInfo.variant == VariantShatranj ||
13003 gameInfo.variant == VariantXiangqi ||
13004 gameInfo.variant == VariantCourier ||
13005 gameInfo.variant == VariantMakruk )
13006 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13011 if(gameInfo.variant == VariantXiangqi)
13012 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13013 if(gameInfo.variant == VariantKnightmate)
13014 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13017 if (gameMode == IcsExamining) {
13018 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13019 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13020 PieceToChar(selection), AAA + x, ONE + y);
13023 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13025 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13026 n = PieceToNumber(selection - BlackPawn);
13027 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13028 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13029 boards[0][BOARD_HEIGHT-1-n][1]++;
13031 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13032 n = PieceToNumber(selection);
13033 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13034 boards[0][n][BOARD_WIDTH-1] = selection;
13035 boards[0][n][BOARD_WIDTH-2]++;
13038 boards[0][y][x] = selection;
13039 DrawPosition(TRUE, boards[0]);
13047 DropMenuEvent(selection, x, y)
13048 ChessSquare selection;
13051 ChessMove moveType;
13053 switch (gameMode) {
13054 case IcsPlayingWhite:
13055 case MachinePlaysBlack:
13056 if (!WhiteOnMove(currentMove)) {
13057 DisplayMoveError(_("It is Black's turn"));
13060 moveType = WhiteDrop;
13062 case IcsPlayingBlack:
13063 case MachinePlaysWhite:
13064 if (WhiteOnMove(currentMove)) {
13065 DisplayMoveError(_("It is White's turn"));
13068 moveType = BlackDrop;
13071 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13077 if (moveType == BlackDrop && selection < BlackPawn) {
13078 selection = (ChessSquare) ((int) selection
13079 + (int) BlackPawn - (int) WhitePawn);
13081 if (boards[currentMove][y][x] != EmptySquare) {
13082 DisplayMoveError(_("That square is occupied"));
13086 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13092 /* Accept a pending offer of any kind from opponent */
13094 if (appData.icsActive) {
13095 SendToICS(ics_prefix);
13096 SendToICS("accept\n");
13097 } else if (cmailMsgLoaded) {
13098 if (currentMove == cmailOldMove &&
13099 commentList[cmailOldMove] != NULL &&
13100 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13101 "Black offers a draw" : "White offers a draw")) {
13103 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13104 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13106 DisplayError(_("There is no pending offer on this move"), 0);
13107 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13110 /* Not used for offers from chess program */
13117 /* Decline a pending offer of any kind from opponent */
13119 if (appData.icsActive) {
13120 SendToICS(ics_prefix);
13121 SendToICS("decline\n");
13122 } else if (cmailMsgLoaded) {
13123 if (currentMove == cmailOldMove &&
13124 commentList[cmailOldMove] != NULL &&
13125 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13126 "Black offers a draw" : "White offers a draw")) {
13128 AppendComment(cmailOldMove, "Draw declined", TRUE);
13129 DisplayComment(cmailOldMove - 1, "Draw declined");
13132 DisplayError(_("There is no pending offer on this move"), 0);
13135 /* Not used for offers from chess program */
13142 /* Issue ICS rematch command */
13143 if (appData.icsActive) {
13144 SendToICS(ics_prefix);
13145 SendToICS("rematch\n");
13152 /* Call your opponent's flag (claim a win on time) */
13153 if (appData.icsActive) {
13154 SendToICS(ics_prefix);
13155 SendToICS("flag\n");
13157 switch (gameMode) {
13160 case MachinePlaysWhite:
13163 GameEnds(GameIsDrawn, "Both players ran out of time",
13166 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13168 DisplayError(_("Your opponent is not out of time"), 0);
13171 case MachinePlaysBlack:
13174 GameEnds(GameIsDrawn, "Both players ran out of time",
13177 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13179 DisplayError(_("Your opponent is not out of time"), 0);
13187 ClockClick(int which)
13188 { // [HGM] code moved to back-end from winboard.c
13189 if(which) { // black clock
13190 if (gameMode == EditPosition || gameMode == IcsExamining) {
13191 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13192 SetBlackToPlayEvent();
13193 } else if (gameMode == EditGame || shiftKey) {
13194 AdjustClock(which, -1);
13195 } else if (gameMode == IcsPlayingWhite ||
13196 gameMode == MachinePlaysBlack) {
13199 } else { // white clock
13200 if (gameMode == EditPosition || gameMode == IcsExamining) {
13201 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13202 SetWhiteToPlayEvent();
13203 } else if (gameMode == EditGame || shiftKey) {
13204 AdjustClock(which, -1);
13205 } else if (gameMode == IcsPlayingBlack ||
13206 gameMode == MachinePlaysWhite) {
13215 /* Offer draw or accept pending draw offer from opponent */
13217 if (appData.icsActive) {
13218 /* Note: tournament rules require draw offers to be
13219 made after you make your move but before you punch
13220 your clock. Currently ICS doesn't let you do that;
13221 instead, you immediately punch your clock after making
13222 a move, but you can offer a draw at any time. */
13224 SendToICS(ics_prefix);
13225 SendToICS("draw\n");
13226 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13227 } else if (cmailMsgLoaded) {
13228 if (currentMove == cmailOldMove &&
13229 commentList[cmailOldMove] != NULL &&
13230 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13231 "Black offers a draw" : "White offers a draw")) {
13232 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13233 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13234 } else if (currentMove == cmailOldMove + 1) {
13235 char *offer = WhiteOnMove(cmailOldMove) ?
13236 "White offers a draw" : "Black offers a draw";
13237 AppendComment(currentMove, offer, TRUE);
13238 DisplayComment(currentMove - 1, offer);
13239 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13241 DisplayError(_("You must make your move before offering a draw"), 0);
13242 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13244 } else if (first.offeredDraw) {
13245 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13247 if (first.sendDrawOffers) {
13248 SendToProgram("draw\n", &first);
13249 userOfferedDraw = TRUE;
13257 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13259 if (appData.icsActive) {
13260 SendToICS(ics_prefix);
13261 SendToICS("adjourn\n");
13263 /* Currently GNU Chess doesn't offer or accept Adjourns */
13271 /* Offer Abort or accept pending Abort offer from opponent */
13273 if (appData.icsActive) {
13274 SendToICS(ics_prefix);
13275 SendToICS("abort\n");
13277 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13284 /* Resign. You can do this even if it's not your turn. */
13286 if (appData.icsActive) {
13287 SendToICS(ics_prefix);
13288 SendToICS("resign\n");
13290 switch (gameMode) {
13291 case MachinePlaysWhite:
13292 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13294 case MachinePlaysBlack:
13295 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13298 if (cmailMsgLoaded) {
13300 if (WhiteOnMove(cmailOldMove)) {
13301 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13303 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13305 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13316 StopObservingEvent()
13318 /* Stop observing current games */
13319 SendToICS(ics_prefix);
13320 SendToICS("unobserve\n");
13324 StopExaminingEvent()
13326 /* Stop observing current game */
13327 SendToICS(ics_prefix);
13328 SendToICS("unexamine\n");
13332 ForwardInner(target)
13337 if (appData.debugMode)
13338 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13339 target, currentMove, forwardMostMove);
13341 if (gameMode == EditPosition)
13344 if (gameMode == PlayFromGameFile && !pausing)
13347 if (gameMode == IcsExamining && pausing)
13348 limit = pauseExamForwardMostMove;
13350 limit = forwardMostMove;
13352 if (target > limit) target = limit;
13354 if (target > 0 && moveList[target - 1][0]) {
13355 int fromX, fromY, toX, toY;
13356 toX = moveList[target - 1][2] - AAA;
13357 toY = moveList[target - 1][3] - ONE;
13358 if (moveList[target - 1][1] == '@') {
13359 if (appData.highlightLastMove) {
13360 SetHighlights(-1, -1, toX, toY);
13363 fromX = moveList[target - 1][0] - AAA;
13364 fromY = moveList[target - 1][1] - ONE;
13365 if (target == currentMove + 1) {
13366 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13368 if (appData.highlightLastMove) {
13369 SetHighlights(fromX, fromY, toX, toY);
13373 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13374 gameMode == Training || gameMode == PlayFromGameFile ||
13375 gameMode == AnalyzeFile) {
13376 while (currentMove < target) {
13377 SendMoveToProgram(currentMove++, &first);
13380 currentMove = target;
13383 if (gameMode == EditGame || gameMode == EndOfGame) {
13384 whiteTimeRemaining = timeRemaining[0][currentMove];
13385 blackTimeRemaining = timeRemaining[1][currentMove];
13387 DisplayBothClocks();
13388 DisplayMove(currentMove - 1);
13389 DrawPosition(FALSE, boards[currentMove]);
13390 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13391 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13392 DisplayComment(currentMove - 1, commentList[currentMove]);
13400 if (gameMode == IcsExamining && !pausing) {
13401 SendToICS(ics_prefix);
13402 SendToICS("forward\n");
13404 ForwardInner(currentMove + 1);
13411 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13412 /* to optimze, we temporarily turn off analysis mode while we feed
13413 * the remaining moves to the engine. Otherwise we get analysis output
13416 if (first.analysisSupport) {
13417 SendToProgram("exit\nforce\n", &first);
13418 first.analyzing = FALSE;
13422 if (gameMode == IcsExamining && !pausing) {
13423 SendToICS(ics_prefix);
13424 SendToICS("forward 999999\n");
13426 ForwardInner(forwardMostMove);
13429 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13430 /* we have fed all the moves, so reactivate analysis mode */
13431 SendToProgram("analyze\n", &first);
13432 first.analyzing = TRUE;
13433 /*first.maybeThinking = TRUE;*/
13434 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13439 BackwardInner(target)
13442 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13444 if (appData.debugMode)
13445 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13446 target, currentMove, forwardMostMove);
13448 if (gameMode == EditPosition) return;
13449 if (currentMove <= backwardMostMove) {
13451 DrawPosition(full_redraw, boards[currentMove]);
13454 if (gameMode == PlayFromGameFile && !pausing)
13457 if (moveList[target][0]) {
13458 int fromX, fromY, toX, toY;
13459 toX = moveList[target][2] - AAA;
13460 toY = moveList[target][3] - ONE;
13461 if (moveList[target][1] == '@') {
13462 if (appData.highlightLastMove) {
13463 SetHighlights(-1, -1, toX, toY);
13466 fromX = moveList[target][0] - AAA;
13467 fromY = moveList[target][1] - ONE;
13468 if (target == currentMove - 1) {
13469 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13471 if (appData.highlightLastMove) {
13472 SetHighlights(fromX, fromY, toX, toY);
13476 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13477 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13478 while (currentMove > target) {
13479 SendToProgram("undo\n", &first);
13483 currentMove = target;
13486 if (gameMode == EditGame || gameMode == EndOfGame) {
13487 whiteTimeRemaining = timeRemaining[0][currentMove];
13488 blackTimeRemaining = timeRemaining[1][currentMove];
13490 DisplayBothClocks();
13491 DisplayMove(currentMove - 1);
13492 DrawPosition(full_redraw, boards[currentMove]);
13493 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13494 // [HGM] PV info: routine tests if comment empty
13495 DisplayComment(currentMove - 1, commentList[currentMove]);
13501 if (gameMode == IcsExamining && !pausing) {
13502 SendToICS(ics_prefix);
13503 SendToICS("backward\n");
13505 BackwardInner(currentMove - 1);
13512 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13513 /* to optimize, we temporarily turn off analysis mode while we undo
13514 * all the moves. Otherwise we get analysis output after each undo.
13516 if (first.analysisSupport) {
13517 SendToProgram("exit\nforce\n", &first);
13518 first.analyzing = FALSE;
13522 if (gameMode == IcsExamining && !pausing) {
13523 SendToICS(ics_prefix);
13524 SendToICS("backward 999999\n");
13526 BackwardInner(backwardMostMove);
13529 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13530 /* we have fed all the moves, so reactivate analysis mode */
13531 SendToProgram("analyze\n", &first);
13532 first.analyzing = TRUE;
13533 /*first.maybeThinking = TRUE;*/
13534 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13541 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13542 if (to >= forwardMostMove) to = forwardMostMove;
13543 if (to <= backwardMostMove) to = backwardMostMove;
13544 if (to < currentMove) {
13552 RevertEvent(Boolean annotate)
13554 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13557 if (gameMode != IcsExamining) {
13558 DisplayError(_("You are not examining a game"), 0);
13562 DisplayError(_("You can't revert while pausing"), 0);
13565 SendToICS(ics_prefix);
13566 SendToICS("revert\n");
13572 switch (gameMode) {
13573 case MachinePlaysWhite:
13574 case MachinePlaysBlack:
13575 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13576 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13579 if (forwardMostMove < 2) return;
13580 currentMove = forwardMostMove = forwardMostMove - 2;
13581 whiteTimeRemaining = timeRemaining[0][currentMove];
13582 blackTimeRemaining = timeRemaining[1][currentMove];
13583 DisplayBothClocks();
13584 DisplayMove(currentMove - 1);
13585 ClearHighlights();/*!! could figure this out*/
13586 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13587 SendToProgram("remove\n", &first);
13588 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13591 case BeginningOfGame:
13595 case IcsPlayingWhite:
13596 case IcsPlayingBlack:
13597 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13598 SendToICS(ics_prefix);
13599 SendToICS("takeback 2\n");
13601 SendToICS(ics_prefix);
13602 SendToICS("takeback 1\n");
13611 ChessProgramState *cps;
13613 switch (gameMode) {
13614 case MachinePlaysWhite:
13615 if (!WhiteOnMove(forwardMostMove)) {
13616 DisplayError(_("It is your turn"), 0);
13621 case MachinePlaysBlack:
13622 if (WhiteOnMove(forwardMostMove)) {
13623 DisplayError(_("It is your turn"), 0);
13628 case TwoMachinesPlay:
13629 if (WhiteOnMove(forwardMostMove) ==
13630 (first.twoMachinesColor[0] == 'w')) {
13636 case BeginningOfGame:
13640 SendToProgram("?\n", cps);
13644 TruncateGameEvent()
13647 if (gameMode != EditGame) return;
13654 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13655 if (forwardMostMove > currentMove) {
13656 if (gameInfo.resultDetails != NULL) {
13657 free(gameInfo.resultDetails);
13658 gameInfo.resultDetails = NULL;
13659 gameInfo.result = GameUnfinished;
13661 forwardMostMove = currentMove;
13662 HistorySet(parseList, backwardMostMove, forwardMostMove,
13670 if (appData.noChessProgram) return;
13671 switch (gameMode) {
13672 case MachinePlaysWhite:
13673 if (WhiteOnMove(forwardMostMove)) {
13674 DisplayError(_("Wait until your turn"), 0);
13678 case BeginningOfGame:
13679 case MachinePlaysBlack:
13680 if (!WhiteOnMove(forwardMostMove)) {
13681 DisplayError(_("Wait until your turn"), 0);
13686 DisplayError(_("No hint available"), 0);
13689 SendToProgram("hint\n", &first);
13690 hintRequested = TRUE;
13696 if (appData.noChessProgram) return;
13697 switch (gameMode) {
13698 case MachinePlaysWhite:
13699 if (WhiteOnMove(forwardMostMove)) {
13700 DisplayError(_("Wait until your turn"), 0);
13704 case BeginningOfGame:
13705 case MachinePlaysBlack:
13706 if (!WhiteOnMove(forwardMostMove)) {
13707 DisplayError(_("Wait until your turn"), 0);
13712 EditPositionDone(TRUE);
13714 case TwoMachinesPlay:
13719 SendToProgram("bk\n", &first);
13720 bookOutput[0] = NULLCHAR;
13721 bookRequested = TRUE;
13727 char *tags = PGNTags(&gameInfo);
13728 TagsPopUp(tags, CmailMsg());
13732 /* end button procedures */
13735 PrintPosition(fp, move)
13741 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13742 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13743 char c = PieceToChar(boards[move][i][j]);
13744 fputc(c == 'x' ? '.' : c, fp);
13745 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13748 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13749 fprintf(fp, "white to play\n");
13751 fprintf(fp, "black to play\n");
13758 if (gameInfo.white != NULL) {
13759 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13765 /* Find last component of program's own name, using some heuristics */
13767 TidyProgramName(prog, host, buf)
13768 char *prog, *host, buf[MSG_SIZ];
13771 int local = (strcmp(host, "localhost") == 0);
13772 while (!local && (p = strchr(prog, ';')) != NULL) {
13774 while (*p == ' ') p++;
13777 if (*prog == '"' || *prog == '\'') {
13778 q = strchr(prog + 1, *prog);
13780 q = strchr(prog, ' ');
13782 if (q == NULL) q = prog + strlen(prog);
13784 while (p >= prog && *p != '/' && *p != '\\') p--;
13786 if(p == prog && *p == '"') p++;
13787 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13788 memcpy(buf, p, q - p);
13789 buf[q - p] = NULLCHAR;
13797 TimeControlTagValue()
13800 if (!appData.clockMode) {
13801 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13802 } else if (movesPerSession > 0) {
13803 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13804 } else if (timeIncrement == 0) {
13805 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13807 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13809 return StrSave(buf);
13815 /* This routine is used only for certain modes */
13816 VariantClass v = gameInfo.variant;
13817 ChessMove r = GameUnfinished;
13820 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13821 r = gameInfo.result;
13822 p = gameInfo.resultDetails;
13823 gameInfo.resultDetails = NULL;
13825 ClearGameInfo(&gameInfo);
13826 gameInfo.variant = v;
13828 switch (gameMode) {
13829 case MachinePlaysWhite:
13830 gameInfo.event = StrSave( appData.pgnEventHeader );
13831 gameInfo.site = StrSave(HostName());
13832 gameInfo.date = PGNDate();
13833 gameInfo.round = StrSave("-");
13834 gameInfo.white = StrSave(first.tidy);
13835 gameInfo.black = StrSave(UserName());
13836 gameInfo.timeControl = TimeControlTagValue();
13839 case MachinePlaysBlack:
13840 gameInfo.event = StrSave( appData.pgnEventHeader );
13841 gameInfo.site = StrSave(HostName());
13842 gameInfo.date = PGNDate();
13843 gameInfo.round = StrSave("-");
13844 gameInfo.white = StrSave(UserName());
13845 gameInfo.black = StrSave(first.tidy);
13846 gameInfo.timeControl = TimeControlTagValue();
13849 case TwoMachinesPlay:
13850 gameInfo.event = StrSave( appData.pgnEventHeader );
13851 gameInfo.site = StrSave(HostName());
13852 gameInfo.date = PGNDate();
13855 snprintf(buf, MSG_SIZ, "%d", roundNr);
13856 gameInfo.round = StrSave(buf);
13858 gameInfo.round = StrSave("-");
13860 if (first.twoMachinesColor[0] == 'w') {
13861 gameInfo.white = StrSave(first.tidy);
13862 gameInfo.black = StrSave(second.tidy);
13864 gameInfo.white = StrSave(second.tidy);
13865 gameInfo.black = StrSave(first.tidy);
13867 gameInfo.timeControl = TimeControlTagValue();
13871 gameInfo.event = StrSave("Edited game");
13872 gameInfo.site = StrSave(HostName());
13873 gameInfo.date = PGNDate();
13874 gameInfo.round = StrSave("-");
13875 gameInfo.white = StrSave("-");
13876 gameInfo.black = StrSave("-");
13877 gameInfo.result = r;
13878 gameInfo.resultDetails = p;
13882 gameInfo.event = StrSave("Edited position");
13883 gameInfo.site = StrSave(HostName());
13884 gameInfo.date = PGNDate();
13885 gameInfo.round = StrSave("-");
13886 gameInfo.white = StrSave("-");
13887 gameInfo.black = StrSave("-");
13890 case IcsPlayingWhite:
13891 case IcsPlayingBlack:
13896 case PlayFromGameFile:
13897 gameInfo.event = StrSave("Game from non-PGN file");
13898 gameInfo.site = StrSave(HostName());
13899 gameInfo.date = PGNDate();
13900 gameInfo.round = StrSave("-");
13901 gameInfo.white = StrSave("?");
13902 gameInfo.black = StrSave("?");
13911 ReplaceComment(index, text)
13919 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13920 pvInfoList[index-1].depth == len &&
13921 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13922 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13923 while (*text == '\n') text++;
13924 len = strlen(text);
13925 while (len > 0 && text[len - 1] == '\n') len--;
13927 if (commentList[index] != NULL)
13928 free(commentList[index]);
13931 commentList[index] = NULL;
13934 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13935 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13936 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13937 commentList[index] = (char *) malloc(len + 2);
13938 strncpy(commentList[index], text, len);
13939 commentList[index][len] = '\n';
13940 commentList[index][len + 1] = NULLCHAR;
13942 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13944 commentList[index] = (char *) malloc(len + 7);
13945 safeStrCpy(commentList[index], "{\n", 3);
13946 safeStrCpy(commentList[index]+2, text, len+1);
13947 commentList[index][len+2] = NULLCHAR;
13948 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13949 strcat(commentList[index], "\n}\n");
13963 if (ch == '\r') continue;
13965 } while (ch != '\0');
13969 AppendComment(index, text, addBraces)
13972 Boolean addBraces; // [HGM] braces: tells if we should add {}
13977 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13978 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13981 while (*text == '\n') text++;
13982 len = strlen(text);
13983 while (len > 0 && text[len - 1] == '\n') len--;
13985 if (len == 0) return;
13987 if (commentList[index] != NULL) {
13988 old = commentList[index];
13989 oldlen = strlen(old);
13990 while(commentList[index][oldlen-1] == '\n')
13991 commentList[index][--oldlen] = NULLCHAR;
13992 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13993 safeStrCpy(commentList[index], old, oldlen + len + 6);
13995 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13996 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13997 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13998 while (*text == '\n') { text++; len--; }
13999 commentList[index][--oldlen] = NULLCHAR;
14001 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14002 else strcat(commentList[index], "\n");
14003 strcat(commentList[index], text);
14004 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14005 else strcat(commentList[index], "\n");
14007 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14009 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14010 else commentList[index][0] = NULLCHAR;
14011 strcat(commentList[index], text);
14012 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14013 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14017 static char * FindStr( char * text, char * sub_text )
14019 char * result = strstr( text, sub_text );
14021 if( result != NULL ) {
14022 result += strlen( sub_text );
14028 /* [AS] Try to extract PV info from PGN comment */
14029 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14030 char *GetInfoFromComment( int index, char * text )
14032 char * sep = text, *p;
14034 if( text != NULL && index > 0 ) {
14037 int time = -1, sec = 0, deci;
14038 char * s_eval = FindStr( text, "[%eval " );
14039 char * s_emt = FindStr( text, "[%emt " );
14041 if( s_eval != NULL || s_emt != NULL ) {
14045 if( s_eval != NULL ) {
14046 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14050 if( delim != ']' ) {
14055 if( s_emt != NULL ) {
14060 /* We expect something like: [+|-]nnn.nn/dd */
14063 if(*text != '{') return text; // [HGM] braces: must be normal comment
14065 sep = strchr( text, '/' );
14066 if( sep == NULL || sep < (text+4) ) {
14071 if(p[1] == '(') { // comment starts with PV
14072 p = strchr(p, ')'); // locate end of PV
14073 if(p == NULL || sep < p+5) return text;
14074 // at this point we have something like "{(.*) +0.23/6 ..."
14075 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14076 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14077 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14079 time = -1; sec = -1; deci = -1;
14080 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14081 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14082 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14083 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14087 if( score_lo < 0 || score_lo >= 100 ) {
14091 if(sec >= 0) time = 600*time + 10*sec; else
14092 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14094 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14096 /* [HGM] PV time: now locate end of PV info */
14097 while( *++sep >= '0' && *sep <= '9'); // strip depth
14099 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14101 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14103 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14104 while(*sep == ' ') sep++;
14115 pvInfoList[index-1].depth = depth;
14116 pvInfoList[index-1].score = score;
14117 pvInfoList[index-1].time = 10*time; // centi-sec
14118 if(*sep == '}') *sep = 0; else *--sep = '{';
14119 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14125 SendToProgram(message, cps)
14127 ChessProgramState *cps;
14129 int count, outCount, error;
14132 if (cps->pr == NULL) return;
14135 if (appData.debugMode) {
14138 fprintf(debugFP, "%ld >%-6s: %s",
14139 SubtractTimeMarks(&now, &programStartTime),
14140 cps->which, message);
14143 count = strlen(message);
14144 outCount = OutputToProcess(cps->pr, message, count, &error);
14145 if (outCount < count && !exiting
14146 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14147 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14148 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14149 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14150 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14151 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14152 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14153 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14155 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14156 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14157 gameInfo.result = res;
14159 gameInfo.resultDetails = StrSave(buf);
14161 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14162 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14167 ReceiveFromProgram(isr, closure, message, count, error)
14168 InputSourceRef isr;
14176 ChessProgramState *cps = (ChessProgramState *)closure;
14178 if (isr != cps->isr) return; /* Killed intentionally */
14181 RemoveInputSource(cps->isr);
14182 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14183 _(cps->which), cps->program);
14184 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14185 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14186 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14187 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14188 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14190 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14191 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14192 gameInfo.result = res;
14194 gameInfo.resultDetails = StrSave(buf);
14196 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14197 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14199 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14200 _(cps->which), cps->program);
14201 RemoveInputSource(cps->isr);
14203 /* [AS] Program is misbehaving badly... kill it */
14204 if( count == -2 ) {
14205 DestroyChildProcess( cps->pr, 9 );
14209 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14214 if ((end_str = strchr(message, '\r')) != NULL)
14215 *end_str = NULLCHAR;
14216 if ((end_str = strchr(message, '\n')) != NULL)
14217 *end_str = NULLCHAR;
14219 if (appData.debugMode) {
14220 TimeMark now; int print = 1;
14221 char *quote = ""; char c; int i;
14223 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14224 char start = message[0];
14225 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14226 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14227 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14228 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14229 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14230 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14231 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14232 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14233 sscanf(message, "hint: %c", &c)!=1 &&
14234 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14235 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14236 print = (appData.engineComments >= 2);
14238 message[0] = start; // restore original message
14242 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14243 SubtractTimeMarks(&now, &programStartTime), cps->which,
14249 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14250 if (appData.icsEngineAnalyze) {
14251 if (strstr(message, "whisper") != NULL ||
14252 strstr(message, "kibitz") != NULL ||
14253 strstr(message, "tellics") != NULL) return;
14256 HandleMachineMove(message, cps);
14261 SendTimeControl(cps, mps, tc, inc, sd, st)
14262 ChessProgramState *cps;
14263 int mps, inc, sd, st;
14269 if( timeControl_2 > 0 ) {
14270 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14271 tc = timeControl_2;
14274 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14275 inc /= cps->timeOdds;
14276 st /= cps->timeOdds;
14278 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14281 /* Set exact time per move, normally using st command */
14282 if (cps->stKludge) {
14283 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14285 if (seconds == 0) {
14286 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14288 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14291 snprintf(buf, MSG_SIZ, "st %d\n", st);
14294 /* Set conventional or incremental time control, using level command */
14295 if (seconds == 0) {
14296 /* Note old gnuchess bug -- minutes:seconds used to not work.
14297 Fixed in later versions, but still avoid :seconds
14298 when seconds is 0. */
14299 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14301 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14302 seconds, inc/1000.);
14305 SendToProgram(buf, cps);
14307 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14308 /* Orthogonally, limit search to given depth */
14310 if (cps->sdKludge) {
14311 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14313 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14315 SendToProgram(buf, cps);
14318 if(cps->nps >= 0) { /* [HGM] nps */
14319 if(cps->supportsNPS == FALSE)
14320 cps->nps = -1; // don't use if engine explicitly says not supported!
14322 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14323 SendToProgram(buf, cps);
14328 ChessProgramState *WhitePlayer()
14329 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14331 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14332 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14338 SendTimeRemaining(cps, machineWhite)
14339 ChessProgramState *cps;
14340 int /*boolean*/ machineWhite;
14342 char message[MSG_SIZ];
14345 /* Note: this routine must be called when the clocks are stopped
14346 or when they have *just* been set or switched; otherwise
14347 it will be off by the time since the current tick started.
14349 if (machineWhite) {
14350 time = whiteTimeRemaining / 10;
14351 otime = blackTimeRemaining / 10;
14353 time = blackTimeRemaining / 10;
14354 otime = whiteTimeRemaining / 10;
14356 /* [HGM] translate opponent's time by time-odds factor */
14357 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14358 if (appData.debugMode) {
14359 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14362 if (time <= 0) time = 1;
14363 if (otime <= 0) otime = 1;
14365 snprintf(message, MSG_SIZ, "time %ld\n", time);
14366 SendToProgram(message, cps);
14368 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14369 SendToProgram(message, cps);
14373 BoolFeature(p, name, loc, cps)
14377 ChessProgramState *cps;
14380 int len = strlen(name);
14383 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14385 sscanf(*p, "%d", &val);
14387 while (**p && **p != ' ')
14389 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14390 SendToProgram(buf, cps);
14397 IntFeature(p, name, loc, cps)
14401 ChessProgramState *cps;
14404 int len = strlen(name);
14405 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14407 sscanf(*p, "%d", loc);
14408 while (**p && **p != ' ') (*p)++;
14409 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14410 SendToProgram(buf, cps);
14417 StringFeature(p, name, loc, cps)
14421 ChessProgramState *cps;
14424 int len = strlen(name);
14425 if (strncmp((*p), name, len) == 0
14426 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14428 sscanf(*p, "%[^\"]", loc);
14429 while (**p && **p != '\"') (*p)++;
14430 if (**p == '\"') (*p)++;
14431 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14432 SendToProgram(buf, cps);
14439 ParseOption(Option *opt, ChessProgramState *cps)
14440 // [HGM] options: process the string that defines an engine option, and determine
14441 // name, type, default value, and allowed value range
14443 char *p, *q, buf[MSG_SIZ];
14444 int n, min = (-1)<<31, max = 1<<31, def;
14446 if(p = strstr(opt->name, " -spin ")) {
14447 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14448 if(max < min) max = min; // enforce consistency
14449 if(def < min) def = min;
14450 if(def > max) def = max;
14455 } else if((p = strstr(opt->name, " -slider "))) {
14456 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14457 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14458 if(max < min) max = min; // enforce consistency
14459 if(def < min) def = min;
14460 if(def > max) def = max;
14464 opt->type = Spin; // Slider;
14465 } else if((p = strstr(opt->name, " -string "))) {
14466 opt->textValue = p+9;
14467 opt->type = TextBox;
14468 } else if((p = strstr(opt->name, " -file "))) {
14469 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14470 opt->textValue = p+7;
14471 opt->type = FileName; // FileName;
14472 } else if((p = strstr(opt->name, " -path "))) {
14473 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14474 opt->textValue = p+7;
14475 opt->type = PathName; // PathName;
14476 } else if(p = strstr(opt->name, " -check ")) {
14477 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14478 opt->value = (def != 0);
14479 opt->type = CheckBox;
14480 } else if(p = strstr(opt->name, " -combo ")) {
14481 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14482 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14483 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14484 opt->value = n = 0;
14485 while(q = StrStr(q, " /// ")) {
14486 n++; *q = 0; // count choices, and null-terminate each of them
14488 if(*q == '*') { // remember default, which is marked with * prefix
14492 cps->comboList[cps->comboCnt++] = q;
14494 cps->comboList[cps->comboCnt++] = NULL;
14496 opt->type = ComboBox;
14497 } else if(p = strstr(opt->name, " -button")) {
14498 opt->type = Button;
14499 } else if(p = strstr(opt->name, " -save")) {
14500 opt->type = SaveButton;
14501 } else return FALSE;
14502 *p = 0; // terminate option name
14503 // now look if the command-line options define a setting for this engine option.
14504 if(cps->optionSettings && cps->optionSettings[0])
14505 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14506 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14507 snprintf(buf, MSG_SIZ, "option %s", p);
14508 if(p = strstr(buf, ",")) *p = 0;
14509 if(q = strchr(buf, '=')) switch(opt->type) {
14511 for(n=0; n<opt->max; n++)
14512 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14515 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14519 opt->value = atoi(q+1);
14524 SendToProgram(buf, cps);
14530 FeatureDone(cps, val)
14531 ChessProgramState* cps;
14534 DelayedEventCallback cb = GetDelayedEvent();
14535 if ((cb == InitBackEnd3 && cps == &first) ||
14536 (cb == SettingsMenuIfReady && cps == &second) ||
14537 (cb == LoadEngine) ||
14538 (cb == TwoMachinesEventIfReady)) {
14539 CancelDelayedEvent();
14540 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14542 cps->initDone = val;
14545 /* Parse feature command from engine */
14547 ParseFeatures(args, cps)
14549 ChessProgramState *cps;
14557 while (*p == ' ') p++;
14558 if (*p == NULLCHAR) return;
14560 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14561 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14562 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14563 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14564 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14565 if (BoolFeature(&p, "reuse", &val, cps)) {
14566 /* Engine can disable reuse, but can't enable it if user said no */
14567 if (!val) cps->reuse = FALSE;
14570 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14571 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14572 if (gameMode == TwoMachinesPlay) {
14573 DisplayTwoMachinesTitle();
14579 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14580 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14581 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14582 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14583 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14584 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14585 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14586 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14587 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14588 if (IntFeature(&p, "done", &val, cps)) {
14589 FeatureDone(cps, val);
14592 /* Added by Tord: */
14593 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14594 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14595 /* End of additions by Tord */
14597 /* [HGM] added features: */
14598 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14599 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14600 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14601 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14602 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14603 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14604 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14605 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14606 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14607 SendToProgram(buf, cps);
14610 if(cps->nrOptions >= MAX_OPTIONS) {
14612 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14613 DisplayError(buf, 0);
14617 /* End of additions by HGM */
14619 /* unknown feature: complain and skip */
14621 while (*q && *q != '=') q++;
14622 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14623 SendToProgram(buf, cps);
14629 while (*p && *p != '\"') p++;
14630 if (*p == '\"') p++;
14632 while (*p && *p != ' ') p++;
14640 PeriodicUpdatesEvent(newState)
14643 if (newState == appData.periodicUpdates)
14646 appData.periodicUpdates=newState;
14648 /* Display type changes, so update it now */
14649 // DisplayAnalysis();
14651 /* Get the ball rolling again... */
14653 AnalysisPeriodicEvent(1);
14654 StartAnalysisClock();
14659 PonderNextMoveEvent(newState)
14662 if (newState == appData.ponderNextMove) return;
14663 if (gameMode == EditPosition) EditPositionDone(TRUE);
14665 SendToProgram("hard\n", &first);
14666 if (gameMode == TwoMachinesPlay) {
14667 SendToProgram("hard\n", &second);
14670 SendToProgram("easy\n", &first);
14671 thinkOutput[0] = NULLCHAR;
14672 if (gameMode == TwoMachinesPlay) {
14673 SendToProgram("easy\n", &second);
14676 appData.ponderNextMove = newState;
14680 NewSettingEvent(option, feature, command, value)
14682 int option, value, *feature;
14686 if (gameMode == EditPosition) EditPositionDone(TRUE);
14687 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14688 if(feature == NULL || *feature) SendToProgram(buf, &first);
14689 if (gameMode == TwoMachinesPlay) {
14690 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14695 ShowThinkingEvent()
14696 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14698 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14699 int newState = appData.showThinking
14700 // [HGM] thinking: other features now need thinking output as well
14701 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14703 if (oldState == newState) return;
14704 oldState = newState;
14705 if (gameMode == EditPosition) EditPositionDone(TRUE);
14707 SendToProgram("post\n", &first);
14708 if (gameMode == TwoMachinesPlay) {
14709 SendToProgram("post\n", &second);
14712 SendToProgram("nopost\n", &first);
14713 thinkOutput[0] = NULLCHAR;
14714 if (gameMode == TwoMachinesPlay) {
14715 SendToProgram("nopost\n", &second);
14718 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14722 AskQuestionEvent(title, question, replyPrefix, which)
14723 char *title; char *question; char *replyPrefix; char *which;
14725 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14726 if (pr == NoProc) return;
14727 AskQuestion(title, question, replyPrefix, pr);
14731 TypeInEvent(char firstChar)
14733 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14734 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14735 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14736 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14737 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14738 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14739 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14740 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14741 gameMode == Training) PopUpMoveDialog(firstChar);
14745 TypeInDoneEvent(char *move)
14748 int n, fromX, fromY, toX, toY;
14750 ChessMove moveType;
\r
14753 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14754 EditPositionPasteFEN(move);
\r
14757 // [HGM] movenum: allow move number to be typed in any mode
\r
14758 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14759 ToNrEvent(2*n-1);
\r
14763 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14764 gameMode != Training) {
\r
14765 DisplayMoveError(_("Displayed move is not current"));
\r
14767 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14768 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14769 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14770 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14771 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14772 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14774 DisplayMoveError(_("Could not parse move"));
\r
14780 DisplayMove(moveNumber)
14783 char message[MSG_SIZ];
14785 char cpThinkOutput[MSG_SIZ];
14787 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14789 if (moveNumber == forwardMostMove - 1 ||
14790 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14792 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14794 if (strchr(cpThinkOutput, '\n')) {
14795 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14798 *cpThinkOutput = NULLCHAR;
14801 /* [AS] Hide thinking from human user */
14802 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14803 *cpThinkOutput = NULLCHAR;
14804 if( thinkOutput[0] != NULLCHAR ) {
14807 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14808 cpThinkOutput[i] = '.';
14810 cpThinkOutput[i] = NULLCHAR;
14811 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14815 if (moveNumber == forwardMostMove - 1 &&
14816 gameInfo.resultDetails != NULL) {
14817 if (gameInfo.resultDetails[0] == NULLCHAR) {
14818 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14820 snprintf(res, MSG_SIZ, " {%s} %s",
14821 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14827 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14828 DisplayMessage(res, cpThinkOutput);
14830 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14831 WhiteOnMove(moveNumber) ? " " : ".. ",
14832 parseList[moveNumber], res);
14833 DisplayMessage(message, cpThinkOutput);
14838 DisplayComment(moveNumber, text)
14842 char title[MSG_SIZ];
14843 char buf[8000]; // comment can be long!
14846 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14847 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14849 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14850 WhiteOnMove(moveNumber) ? " " : ".. ",
14851 parseList[moveNumber]);
14853 // [HGM] PV info: display PV info together with (or as) comment
14854 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14855 if(text == NULL) text = "";
14856 score = pvInfoList[moveNumber].score;
14857 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14858 depth, (pvInfoList[moveNumber].time+50)/100, text);
14861 if (text != NULL && (appData.autoDisplayComment || commentUp))
14862 CommentPopUp(title, text);
14865 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14866 * might be busy thinking or pondering. It can be omitted if your
14867 * gnuchess is configured to stop thinking immediately on any user
14868 * input. However, that gnuchess feature depends on the FIONREAD
14869 * ioctl, which does not work properly on some flavors of Unix.
14873 ChessProgramState *cps;
14876 if (!cps->useSigint) return;
14877 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14878 switch (gameMode) {
14879 case MachinePlaysWhite:
14880 case MachinePlaysBlack:
14881 case TwoMachinesPlay:
14882 case IcsPlayingWhite:
14883 case IcsPlayingBlack:
14886 /* Skip if we know it isn't thinking */
14887 if (!cps->maybeThinking) return;
14888 if (appData.debugMode)
14889 fprintf(debugFP, "Interrupting %s\n", cps->which);
14890 InterruptChildProcess(cps->pr);
14891 cps->maybeThinking = FALSE;
14896 #endif /*ATTENTION*/
14902 if (whiteTimeRemaining <= 0) {
14905 if (appData.icsActive) {
14906 if (appData.autoCallFlag &&
14907 gameMode == IcsPlayingBlack && !blackFlag) {
14908 SendToICS(ics_prefix);
14909 SendToICS("flag\n");
14913 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14915 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14916 if (appData.autoCallFlag) {
14917 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14924 if (blackTimeRemaining <= 0) {
14927 if (appData.icsActive) {
14928 if (appData.autoCallFlag &&
14929 gameMode == IcsPlayingWhite && !whiteFlag) {
14930 SendToICS(ics_prefix);
14931 SendToICS("flag\n");
14935 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14937 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14938 if (appData.autoCallFlag) {
14939 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14952 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14953 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14956 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14958 if ( !WhiteOnMove(forwardMostMove) ) {
14959 /* White made time control */
14960 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14961 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14962 /* [HGM] time odds: correct new time quota for time odds! */
14963 / WhitePlayer()->timeOdds;
14964 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14966 lastBlack -= blackTimeRemaining;
14967 /* Black made time control */
14968 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14969 / WhitePlayer()->other->timeOdds;
14970 lastWhite = whiteTimeRemaining;
14975 DisplayBothClocks()
14977 int wom = gameMode == EditPosition ?
14978 !blackPlaysFirst : WhiteOnMove(currentMove);
14979 DisplayWhiteClock(whiteTimeRemaining, wom);
14980 DisplayBlackClock(blackTimeRemaining, !wom);
14984 /* Timekeeping seems to be a portability nightmare. I think everyone
14985 has ftime(), but I'm really not sure, so I'm including some ifdefs
14986 to use other calls if you don't. Clocks will be less accurate if
14987 you have neither ftime nor gettimeofday.
14990 /* VS 2008 requires the #include outside of the function */
14991 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14992 #include <sys/timeb.h>
14995 /* Get the current time as a TimeMark */
15000 #if HAVE_GETTIMEOFDAY
15002 struct timeval timeVal;
15003 struct timezone timeZone;
15005 gettimeofday(&timeVal, &timeZone);
15006 tm->sec = (long) timeVal.tv_sec;
15007 tm->ms = (int) (timeVal.tv_usec / 1000L);
15009 #else /*!HAVE_GETTIMEOFDAY*/
15012 // include <sys/timeb.h> / moved to just above start of function
15013 struct timeb timeB;
15016 tm->sec = (long) timeB.time;
15017 tm->ms = (int) timeB.millitm;
15019 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15020 tm->sec = (long) time(NULL);
15026 /* Return the difference in milliseconds between two
15027 time marks. We assume the difference will fit in a long!
15030 SubtractTimeMarks(tm2, tm1)
15031 TimeMark *tm2, *tm1;
15033 return 1000L*(tm2->sec - tm1->sec) +
15034 (long) (tm2->ms - tm1->ms);
15039 * Code to manage the game clocks.
15041 * In tournament play, black starts the clock and then white makes a move.
15042 * We give the human user a slight advantage if he is playing white---the
15043 * clocks don't run until he makes his first move, so it takes zero time.
15044 * Also, we don't account for network lag, so we could get out of sync
15045 * with GNU Chess's clock -- but then, referees are always right.
15048 static TimeMark tickStartTM;
15049 static long intendedTickLength;
15052 NextTickLength(timeRemaining)
15053 long timeRemaining;
15055 long nominalTickLength, nextTickLength;
15057 if (timeRemaining > 0L && timeRemaining <= 10000L)
15058 nominalTickLength = 100L;
15060 nominalTickLength = 1000L;
15061 nextTickLength = timeRemaining % nominalTickLength;
15062 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15064 return nextTickLength;
15067 /* Adjust clock one minute up or down */
15069 AdjustClock(Boolean which, int dir)
15071 if(which) blackTimeRemaining += 60000*dir;
15072 else whiteTimeRemaining += 60000*dir;
15073 DisplayBothClocks();
15076 /* Stop clocks and reset to a fresh time control */
15080 (void) StopClockTimer();
15081 if (appData.icsActive) {
15082 whiteTimeRemaining = blackTimeRemaining = 0;
15083 } else if (searchTime) {
15084 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15085 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15086 } else { /* [HGM] correct new time quote for time odds */
15087 whiteTC = blackTC = fullTimeControlString;
15088 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15089 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15091 if (whiteFlag || blackFlag) {
15093 whiteFlag = blackFlag = FALSE;
15095 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15096 DisplayBothClocks();
15099 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15101 /* Decrement running clock by amount of time that has passed */
15105 long timeRemaining;
15106 long lastTickLength, fudge;
15109 if (!appData.clockMode) return;
15110 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15114 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15116 /* Fudge if we woke up a little too soon */
15117 fudge = intendedTickLength - lastTickLength;
15118 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15120 if (WhiteOnMove(forwardMostMove)) {
15121 if(whiteNPS >= 0) lastTickLength = 0;
15122 timeRemaining = whiteTimeRemaining -= lastTickLength;
15123 if(timeRemaining < 0 && !appData.icsActive) {
15124 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15125 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15126 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15127 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15130 DisplayWhiteClock(whiteTimeRemaining - fudge,
15131 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15133 if(blackNPS >= 0) lastTickLength = 0;
15134 timeRemaining = blackTimeRemaining -= lastTickLength;
15135 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15136 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15138 blackStartMove = forwardMostMove;
15139 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15142 DisplayBlackClock(blackTimeRemaining - fudge,
15143 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15145 if (CheckFlags()) return;
15148 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15149 StartClockTimer(intendedTickLength);
15151 /* if the time remaining has fallen below the alarm threshold, sound the
15152 * alarm. if the alarm has sounded and (due to a takeback or time control
15153 * with increment) the time remaining has increased to a level above the
15154 * threshold, reset the alarm so it can sound again.
15157 if (appData.icsActive && appData.icsAlarm) {
15159 /* make sure we are dealing with the user's clock */
15160 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15161 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15164 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15165 alarmSounded = FALSE;
15166 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15168 alarmSounded = TRUE;
15174 /* A player has just moved, so stop the previously running
15175 clock and (if in clock mode) start the other one.
15176 We redisplay both clocks in case we're in ICS mode, because
15177 ICS gives us an update to both clocks after every move.
15178 Note that this routine is called *after* forwardMostMove
15179 is updated, so the last fractional tick must be subtracted
15180 from the color that is *not* on move now.
15183 SwitchClocks(int newMoveNr)
15185 long lastTickLength;
15187 int flagged = FALSE;
15191 if (StopClockTimer() && appData.clockMode) {
15192 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15193 if (!WhiteOnMove(forwardMostMove)) {
15194 if(blackNPS >= 0) lastTickLength = 0;
15195 blackTimeRemaining -= lastTickLength;
15196 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15197 // if(pvInfoList[forwardMostMove].time == -1)
15198 pvInfoList[forwardMostMove].time = // use GUI time
15199 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15201 if(whiteNPS >= 0) lastTickLength = 0;
15202 whiteTimeRemaining -= lastTickLength;
15203 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15204 // if(pvInfoList[forwardMostMove].time == -1)
15205 pvInfoList[forwardMostMove].time =
15206 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15208 flagged = CheckFlags();
15210 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15211 CheckTimeControl();
15213 if (flagged || !appData.clockMode) return;
15215 switch (gameMode) {
15216 case MachinePlaysBlack:
15217 case MachinePlaysWhite:
15218 case BeginningOfGame:
15219 if (pausing) return;
15223 case PlayFromGameFile:
15231 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15232 if(WhiteOnMove(forwardMostMove))
15233 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15234 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15238 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15239 whiteTimeRemaining : blackTimeRemaining);
15240 StartClockTimer(intendedTickLength);
15244 /* Stop both clocks */
15248 long lastTickLength;
15251 if (!StopClockTimer()) return;
15252 if (!appData.clockMode) return;
15256 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15257 if (WhiteOnMove(forwardMostMove)) {
15258 if(whiteNPS >= 0) lastTickLength = 0;
15259 whiteTimeRemaining -= lastTickLength;
15260 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15262 if(blackNPS >= 0) lastTickLength = 0;
15263 blackTimeRemaining -= lastTickLength;
15264 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15269 /* Start clock of player on move. Time may have been reset, so
15270 if clock is already running, stop and restart it. */
15274 (void) StopClockTimer(); /* in case it was running already */
15275 DisplayBothClocks();
15276 if (CheckFlags()) return;
15278 if (!appData.clockMode) return;
15279 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15281 GetTimeMark(&tickStartTM);
15282 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15283 whiteTimeRemaining : blackTimeRemaining);
15285 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15286 whiteNPS = blackNPS = -1;
15287 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15288 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15289 whiteNPS = first.nps;
15290 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15291 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15292 blackNPS = first.nps;
15293 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15294 whiteNPS = second.nps;
15295 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15296 blackNPS = second.nps;
15297 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15299 StartClockTimer(intendedTickLength);
15306 long second, minute, hour, day;
15308 static char buf[32];
15310 if (ms > 0 && ms <= 9900) {
15311 /* convert milliseconds to tenths, rounding up */
15312 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15314 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15318 /* convert milliseconds to seconds, rounding up */
15319 /* use floating point to avoid strangeness of integer division
15320 with negative dividends on many machines */
15321 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15328 day = second / (60 * 60 * 24);
15329 second = second % (60 * 60 * 24);
15330 hour = second / (60 * 60);
15331 second = second % (60 * 60);
15332 minute = second / 60;
15333 second = second % 60;
15336 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15337 sign, day, hour, minute, second);
15339 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15341 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15348 * This is necessary because some C libraries aren't ANSI C compliant yet.
15351 StrStr(string, match)
15352 char *string, *match;
15356 length = strlen(match);
15358 for (i = strlen(string) - length; i >= 0; i--, string++)
15359 if (!strncmp(match, string, length))
15366 StrCaseStr(string, match)
15367 char *string, *match;
15371 length = strlen(match);
15373 for (i = strlen(string) - length; i >= 0; i--, string++) {
15374 for (j = 0; j < length; j++) {
15375 if (ToLower(match[j]) != ToLower(string[j]))
15378 if (j == length) return string;
15392 c1 = ToLower(*s1++);
15393 c2 = ToLower(*s2++);
15394 if (c1 > c2) return 1;
15395 if (c1 < c2) return -1;
15396 if (c1 == NULLCHAR) return 0;
15405 return isupper(c) ? tolower(c) : c;
15413 return islower(c) ? toupper(c) : c;
15415 #endif /* !_amigados */
15423 if ((ret = (char *) malloc(strlen(s) + 1)))
15425 safeStrCpy(ret, s, strlen(s)+1);
15431 StrSavePtr(s, savePtr)
15432 char *s, **savePtr;
15437 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15438 safeStrCpy(*savePtr, s, strlen(s)+1);
15450 clock = time((time_t *)NULL);
15451 tm = localtime(&clock);
15452 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15453 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15454 return StrSave(buf);
15459 PositionToFEN(move, overrideCastling)
15461 char *overrideCastling;
15463 int i, j, fromX, fromY, toX, toY;
15470 whiteToPlay = (gameMode == EditPosition) ?
15471 !blackPlaysFirst : (move % 2 == 0);
15474 /* Piece placement data */
15475 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15477 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15478 if (boards[move][i][j] == EmptySquare) {
15480 } else { ChessSquare piece = boards[move][i][j];
15481 if (emptycount > 0) {
15482 if(emptycount<10) /* [HGM] can be >= 10 */
15483 *p++ = '0' + emptycount;
15484 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15487 if(PieceToChar(piece) == '+') {
15488 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15490 piece = (ChessSquare)(DEMOTED piece);
15492 *p++ = PieceToChar(piece);
15494 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15495 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15500 if (emptycount > 0) {
15501 if(emptycount<10) /* [HGM] can be >= 10 */
15502 *p++ = '0' + emptycount;
15503 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15510 /* [HGM] print Crazyhouse or Shogi holdings */
15511 if( gameInfo.holdingsWidth ) {
15512 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15514 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15515 piece = boards[move][i][BOARD_WIDTH-1];
15516 if( piece != EmptySquare )
15517 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15518 *p++ = PieceToChar(piece);
15520 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15521 piece = boards[move][BOARD_HEIGHT-i-1][0];
15522 if( piece != EmptySquare )
15523 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15524 *p++ = PieceToChar(piece);
15527 if( q == p ) *p++ = '-';
15533 *p++ = whiteToPlay ? 'w' : 'b';
15536 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15537 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15539 if(nrCastlingRights) {
15541 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15542 /* [HGM] write directly from rights */
15543 if(boards[move][CASTLING][2] != NoRights &&
15544 boards[move][CASTLING][0] != NoRights )
15545 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15546 if(boards[move][CASTLING][2] != NoRights &&
15547 boards[move][CASTLING][1] != NoRights )
15548 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15549 if(boards[move][CASTLING][5] != NoRights &&
15550 boards[move][CASTLING][3] != NoRights )
15551 *p++ = boards[move][CASTLING][3] + AAA;
15552 if(boards[move][CASTLING][5] != NoRights &&
15553 boards[move][CASTLING][4] != NoRights )
15554 *p++ = boards[move][CASTLING][4] + AAA;
15557 /* [HGM] write true castling rights */
15558 if( nrCastlingRights == 6 ) {
15559 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15560 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15561 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15562 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15563 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15564 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15565 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15566 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15569 if (q == p) *p++ = '-'; /* No castling rights */
15573 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15574 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15575 /* En passant target square */
15576 if (move > backwardMostMove) {
15577 fromX = moveList[move - 1][0] - AAA;
15578 fromY = moveList[move - 1][1] - ONE;
15579 toX = moveList[move - 1][2] - AAA;
15580 toY = moveList[move - 1][3] - ONE;
15581 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15582 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15583 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15585 /* 2-square pawn move just happened */
15587 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15591 } else if(move == backwardMostMove) {
15592 // [HGM] perhaps we should always do it like this, and forget the above?
15593 if((signed char)boards[move][EP_STATUS] >= 0) {
15594 *p++ = boards[move][EP_STATUS] + AAA;
15595 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15606 /* [HGM] find reversible plies */
15607 { int i = 0, j=move;
15609 if (appData.debugMode) { int k;
15610 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15611 for(k=backwardMostMove; k<=forwardMostMove; k++)
15612 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15616 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15617 if( j == backwardMostMove ) i += initialRulePlies;
15618 sprintf(p, "%d ", i);
15619 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15621 /* Fullmove number */
15622 sprintf(p, "%d", (move / 2) + 1);
15624 return StrSave(buf);
15628 ParseFEN(board, blackPlaysFirst, fen)
15630 int *blackPlaysFirst;
15640 /* [HGM] by default clear Crazyhouse holdings, if present */
15641 if(gameInfo.holdingsWidth) {
15642 for(i=0; i<BOARD_HEIGHT; i++) {
15643 board[i][0] = EmptySquare; /* black holdings */
15644 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15645 board[i][1] = (ChessSquare) 0; /* black counts */
15646 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15650 /* Piece placement data */
15651 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15654 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15655 if (*p == '/') p++;
15656 emptycount = gameInfo.boardWidth - j;
15657 while (emptycount--)
15658 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15660 #if(BOARD_FILES >= 10)
15661 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15662 p++; emptycount=10;
15663 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15664 while (emptycount--)
15665 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15667 } else if (isdigit(*p)) {
15668 emptycount = *p++ - '0';
15669 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15670 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15671 while (emptycount--)
15672 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15673 } else if (*p == '+' || isalpha(*p)) {
15674 if (j >= gameInfo.boardWidth) return FALSE;
15676 piece = CharToPiece(*++p);
15677 if(piece == EmptySquare) return FALSE; /* unknown piece */
15678 piece = (ChessSquare) (PROMOTED piece ); p++;
15679 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15680 } else piece = CharToPiece(*p++);
15682 if(piece==EmptySquare) return FALSE; /* unknown piece */
15683 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15684 piece = (ChessSquare) (PROMOTED piece);
15685 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15688 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15694 while (*p == '/' || *p == ' ') p++;
15696 /* [HGM] look for Crazyhouse holdings here */
15697 while(*p==' ') p++;
15698 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15700 if(*p == '-' ) p++; /* empty holdings */ else {
15701 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15702 /* if we would allow FEN reading to set board size, we would */
15703 /* have to add holdings and shift the board read so far here */
15704 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15706 if((int) piece >= (int) BlackPawn ) {
15707 i = (int)piece - (int)BlackPawn;
15708 i = PieceToNumber((ChessSquare)i);
15709 if( i >= gameInfo.holdingsSize ) return FALSE;
15710 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15711 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15713 i = (int)piece - (int)WhitePawn;
15714 i = PieceToNumber((ChessSquare)i);
15715 if( i >= gameInfo.holdingsSize ) return FALSE;
15716 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15717 board[i][BOARD_WIDTH-2]++; /* black holdings */
15724 while(*p == ' ') p++;
15728 if(appData.colorNickNames) {
15729 if( c == appData.colorNickNames[0] ) c = 'w'; else
15730 if( c == appData.colorNickNames[1] ) c = 'b';
15734 *blackPlaysFirst = FALSE;
15737 *blackPlaysFirst = TRUE;
15743 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15744 /* return the extra info in global variiables */
15746 /* set defaults in case FEN is incomplete */
15747 board[EP_STATUS] = EP_UNKNOWN;
15748 for(i=0; i<nrCastlingRights; i++ ) {
15749 board[CASTLING][i] =
15750 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15751 } /* assume possible unless obviously impossible */
15752 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15753 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15754 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15755 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15756 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15757 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15758 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15759 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15762 while(*p==' ') p++;
15763 if(nrCastlingRights) {
15764 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15765 /* castling indicator present, so default becomes no castlings */
15766 for(i=0; i<nrCastlingRights; i++ ) {
15767 board[CASTLING][i] = NoRights;
15770 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15771 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15772 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15773 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15774 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15776 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15777 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15778 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15780 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15781 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15782 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15783 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15784 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15785 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15788 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15789 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15790 board[CASTLING][2] = whiteKingFile;
15793 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15794 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15795 board[CASTLING][2] = whiteKingFile;
15798 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15799 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15800 board[CASTLING][5] = blackKingFile;
15803 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15804 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15805 board[CASTLING][5] = blackKingFile;
15808 default: /* FRC castlings */
15809 if(c >= 'a') { /* black rights */
15810 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15811 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15812 if(i == BOARD_RGHT) break;
15813 board[CASTLING][5] = i;
15815 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15816 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15818 board[CASTLING][3] = c;
15820 board[CASTLING][4] = c;
15821 } else { /* white rights */
15822 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15823 if(board[0][i] == WhiteKing) break;
15824 if(i == BOARD_RGHT) break;
15825 board[CASTLING][2] = i;
15826 c -= AAA - 'a' + 'A';
15827 if(board[0][c] >= WhiteKing) break;
15829 board[CASTLING][0] = c;
15831 board[CASTLING][1] = c;
15835 for(i=0; i<nrCastlingRights; i++)
15836 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15837 if (appData.debugMode) {
15838 fprintf(debugFP, "FEN castling rights:");
15839 for(i=0; i<nrCastlingRights; i++)
15840 fprintf(debugFP, " %d", board[CASTLING][i]);
15841 fprintf(debugFP, "\n");
15844 while(*p==' ') p++;
15847 /* read e.p. field in games that know e.p. capture */
15848 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15849 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15851 p++; board[EP_STATUS] = EP_NONE;
15853 char c = *p++ - AAA;
15855 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15856 if(*p >= '0' && *p <='9') p++;
15857 board[EP_STATUS] = c;
15862 if(sscanf(p, "%d", &i) == 1) {
15863 FENrulePlies = i; /* 50-move ply counter */
15864 /* (The move number is still ignored) */
15871 EditPositionPasteFEN(char *fen)
15874 Board initial_position;
15876 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15877 DisplayError(_("Bad FEN position in clipboard"), 0);
15880 int savedBlackPlaysFirst = blackPlaysFirst;
15881 EditPositionEvent();
15882 blackPlaysFirst = savedBlackPlaysFirst;
15883 CopyBoard(boards[0], initial_position);
15884 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15885 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15886 DisplayBothClocks();
15887 DrawPosition(FALSE, boards[currentMove]);
15892 static char cseq[12] = "\\ ";
15894 Boolean set_cont_sequence(char *new_seq)
15899 // handle bad attempts to set the sequence
15901 return 0; // acceptable error - no debug
15903 len = strlen(new_seq);
15904 ret = (len > 0) && (len < sizeof(cseq));
15906 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15907 else if (appData.debugMode)
15908 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15913 reformat a source message so words don't cross the width boundary. internal
15914 newlines are not removed. returns the wrapped size (no null character unless
15915 included in source message). If dest is NULL, only calculate the size required
15916 for the dest buffer. lp argument indicats line position upon entry, and it's
15917 passed back upon exit.
15919 int wrap(char *dest, char *src, int count, int width, int *lp)
15921 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15923 cseq_len = strlen(cseq);
15924 old_line = line = *lp;
15925 ansi = len = clen = 0;
15927 for (i=0; i < count; i++)
15929 if (src[i] == '\033')
15932 // if we hit the width, back up
15933 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15935 // store i & len in case the word is too long
15936 old_i = i, old_len = len;
15938 // find the end of the last word
15939 while (i && src[i] != ' ' && src[i] != '\n')
15945 // word too long? restore i & len before splitting it
15946 if ((old_i-i+clen) >= width)
15953 if (i && src[i-1] == ' ')
15956 if (src[i] != ' ' && src[i] != '\n')
15963 // now append the newline and continuation sequence
15968 strncpy(dest+len, cseq, cseq_len);
15976 dest[len] = src[i];
15980 if (src[i] == '\n')
15985 if (dest && appData.debugMode)
15987 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15988 count, width, line, len, *lp);
15989 show_bytes(debugFP, src, count);
15990 fprintf(debugFP, "\ndest: ");
15991 show_bytes(debugFP, dest, len);
15992 fprintf(debugFP, "\n");
15994 *lp = dest ? line : old_line;
15999 // [HGM] vari: routines for shelving variations
16002 PushTail(int firstMove, int lastMove)
16004 int i, j, nrMoves = lastMove - firstMove;
16006 if(appData.icsActive) { // only in local mode
16007 forwardMostMove = currentMove; // mimic old ICS behavior
16010 if(storedGames >= MAX_VARIATIONS-1) return;
16012 // push current tail of game on stack
16013 savedResult[storedGames] = gameInfo.result;
16014 savedDetails[storedGames] = gameInfo.resultDetails;
16015 gameInfo.resultDetails = NULL;
16016 savedFirst[storedGames] = firstMove;
16017 savedLast [storedGames] = lastMove;
16018 savedFramePtr[storedGames] = framePtr;
16019 framePtr -= nrMoves; // reserve space for the boards
16020 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16021 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16022 for(j=0; j<MOVE_LEN; j++)
16023 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16024 for(j=0; j<2*MOVE_LEN; j++)
16025 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16026 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16027 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16028 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16029 pvInfoList[firstMove+i-1].depth = 0;
16030 commentList[framePtr+i] = commentList[firstMove+i];
16031 commentList[firstMove+i] = NULL;
16035 forwardMostMove = firstMove; // truncate game so we can start variation
16036 if(storedGames == 1) GreyRevert(FALSE);
16040 PopTail(Boolean annotate)
16043 char buf[8000], moveBuf[20];
16045 if(appData.icsActive) return FALSE; // only in local mode
16046 if(!storedGames) return FALSE; // sanity
16047 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16050 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16051 nrMoves = savedLast[storedGames] - currentMove;
16054 if(!WhiteOnMove(currentMove))
16055 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16056 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16057 for(i=currentMove; i<forwardMostMove; i++) {
16059 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16060 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16061 strcat(buf, moveBuf);
16062 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16063 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16067 for(i=1; i<=nrMoves; i++) { // copy last variation back
16068 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16069 for(j=0; j<MOVE_LEN; j++)
16070 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16071 for(j=0; j<2*MOVE_LEN; j++)
16072 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16073 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16074 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16075 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16076 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16077 commentList[currentMove+i] = commentList[framePtr+i];
16078 commentList[framePtr+i] = NULL;
16080 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16081 framePtr = savedFramePtr[storedGames];
16082 gameInfo.result = savedResult[storedGames];
16083 if(gameInfo.resultDetails != NULL) {
16084 free(gameInfo.resultDetails);
16086 gameInfo.resultDetails = savedDetails[storedGames];
16087 forwardMostMove = currentMove + nrMoves;
16088 if(storedGames == 0) GreyRevert(TRUE);
16094 { // remove all shelved variations
16096 for(i=0; i<storedGames; i++) {
16097 if(savedDetails[i])
16098 free(savedDetails[i]);
16099 savedDetails[i] = NULL;
16101 for(i=framePtr; i<MAX_MOVES; i++) {
16102 if(commentList[i]) free(commentList[i]);
16103 commentList[i] = NULL;
16105 framePtr = MAX_MOVES-1;
16110 LoadVariation(int index, char *text)
16111 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16112 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16113 int level = 0, move;
16115 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16116 // first find outermost bracketing variation
16117 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16118 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16119 if(*p == '{') wait = '}'; else
16120 if(*p == '[') wait = ']'; else
16121 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16122 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16124 if(*p == wait) wait = NULLCHAR; // closing ]} found
16127 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16128 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16129 end[1] = NULLCHAR; // clip off comment beyond variation
16130 ToNrEvent(currentMove-1);
16131 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16132 // kludge: use ParsePV() to append variation to game
16133 move = currentMove;
16134 ParsePV(start, TRUE);
16135 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16136 ClearPremoveHighlights();
16138 ToNrEvent(currentMove+1);