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));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
260 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
283 /* States for ics_getting_history */
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
291 /* whosays values for GameEnds */
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
303 /* Different types of move when calling RegisterMove */
305 #define CMAIL_RESIGN 1
307 #define CMAIL_ACCEPT 3
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
314 /* Telnet protocol constants */
325 safeStrCpy( char *dst, const char *src, size_t count )
328 assert( dst != NULL );
329 assert( src != NULL );
332 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333 if( i == count && dst[count-1] != NULLCHAR)
335 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336 if(appData.debugMode)
337 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
343 /* Some compiler can't cast u64 to double
344 * This function do the job for us:
346 * We use the highest bit for cast, this only
347 * works if the highest bit is not
348 * in use (This should not happen)
350 * We used this for all compiler
353 u64ToDouble(u64 value)
356 u64 tmp = value & u64Const(0x7fffffffffffffff);
357 r = (double)(s64)tmp;
358 if (value & u64Const(0x8000000000000000))
359 r += 9.2233720368547758080e18; /* 2^63 */
363 /* Fake up flags for now, as we aren't keeping track of castling
364 availability yet. [HGM] Change of logic: the flag now only
365 indicates the type of castlings allowed by the rule of the game.
366 The actual rights themselves are maintained in the array
367 castlingRights, as part of the game history, and are not probed
373 int flags = F_ALL_CASTLE_OK;
374 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375 switch (gameInfo.variant) {
377 flags &= ~F_ALL_CASTLE_OK;
378 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379 flags |= F_IGNORE_CHECK;
381 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386 case VariantKriegspiel:
387 flags |= F_KRIEGSPIEL_CAPTURE;
389 case VariantCapaRandom:
390 case VariantFischeRandom:
391 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392 case VariantNoCastle:
393 case VariantShatranj:
396 flags &= ~F_ALL_CASTLE_OK;
404 FILE *gameFileFP, *debugFP;
407 [AS] Note: sometimes, the sscanf() function is used to parse the input
408 into a fixed-size buffer. Because of this, we must be prepared to
409 receive strings as long as the size of the input buffer, which is currently
410 set to 4K for Windows and 8K for the rest.
411 So, we must either allocate sufficiently large buffers here, or
412 reduce the size of the input buffer in the input reading part.
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
419 ChessProgramState first, second;
421 /* premove variables */
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
462 int have_sent_ICS_logon = 0;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void CleanupTail P((void));
511 ChessSquare FIDEArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515 BlackKing, BlackBishop, BlackKnight, BlackRook }
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522 BlackKing, BlackKing, BlackKnight, BlackRook }
525 ChessSquare KnightmateArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528 { BlackRook, BlackMan, BlackBishop, BlackQueen,
529 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackMan, BlackFerz,
557 BlackKing, BlackMan, BlackKnight, BlackRook }
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 #define GothicArray CapablancaArray
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
616 #define FalconArray CapablancaArray
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
638 Board initialPosition;
641 /* Convert str to a rating. Checks for special cases of "----",
643 "++++", etc. Also strips ()'s */
645 string_to_rating(str)
648 while(*str && !isdigit(*str)) ++str;
650 return 0; /* One of the special "no rating" cases */
658 /* Init programStats */
659 programStats.movelist[0] = 0;
660 programStats.depth = 0;
661 programStats.nr_moves = 0;
662 programStats.moves_left = 0;
663 programStats.nodes = 0;
664 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
665 programStats.score = 0;
666 programStats.got_only_move = 0;
667 programStats.got_fail = 0;
668 programStats.line_is_book = 0;
673 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674 if (appData.firstPlaysBlack) {
675 first.twoMachinesColor = "black\n";
676 second.twoMachinesColor = "white\n";
678 first.twoMachinesColor = "white\n";
679 second.twoMachinesColor = "black\n";
682 first.other = &second;
683 second.other = &first;
686 if(appData.timeOddsMode) {
687 norm = appData.timeOdds[0];
688 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690 first.timeOdds = appData.timeOdds[0]/norm;
691 second.timeOdds = appData.timeOdds[1]/norm;
694 if(programVersion) free(programVersion);
695 if (appData.noChessProgram) {
696 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697 sprintf(programVersion, "%s", PACKAGE_STRING);
699 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706 UnloadEngine(ChessProgramState *cps)
708 /* Kill off first chess program */
709 if (cps->isr != NULL)
710 RemoveInputSource(cps->isr);
713 if (cps->pr != NoProc) {
715 DoSleep( appData.delayBeforeQuit );
716 SendToProgram("quit\n", cps);
717 DoSleep( appData.delayAfterQuit );
718 DestroyChildProcess(cps->pr, cps->useSigterm);
721 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 ClearOptions(ChessProgramState *cps)
728 cps->nrOptions = cps->comboCnt = 0;
729 for(i=0; i<MAX_OPTIONS; i++) {
730 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731 cps->option[i].textValue = 0;
735 char *engineNames[] = {
741 InitEngine(ChessProgramState *cps, int n)
742 { // [HGM] all engine initialiation put in a function that does one engine
746 cps->which = engineNames[n];
747 cps->maybeThinking = FALSE;
751 cps->sendDrawOffers = 1;
753 cps->program = appData.chessProgram[n];
754 cps->host = appData.host[n];
755 cps->dir = appData.directory[n];
756 cps->initString = appData.engInitString[n];
757 cps->computerString = appData.computerString[n];
758 cps->useSigint = TRUE;
759 cps->useSigterm = TRUE;
760 cps->reuse = appData.reuse[n];
761 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
762 cps->useSetboard = FALSE;
764 cps->usePing = FALSE;
767 cps->usePlayother = FALSE;
768 cps->useColors = TRUE;
769 cps->useUsermove = FALSE;
770 cps->sendICS = FALSE;
771 cps->sendName = appData.icsActive;
772 cps->sdKludge = FALSE;
773 cps->stKludge = FALSE;
774 TidyProgramName(cps->program, cps->host, cps->tidy);
776 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777 cps->analysisSupport = 2; /* detect */
778 cps->analyzing = FALSE;
779 cps->initDone = FALSE;
781 /* New features added by Tord: */
782 cps->useFEN960 = FALSE;
783 cps->useOOCastle = TRUE;
784 /* End of new features added by Tord. */
785 cps->fenOverride = appData.fenOverride[n];
787 /* [HGM] time odds: set factor for each machine */
788 cps->timeOdds = appData.timeOdds[n];
790 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791 cps->accumulateTC = appData.accumulateTC[n];
792 cps->maxNrOfSessions = 1;
796 cps->supportsNPS = UNKNOWN;
799 cps->optionSettings = appData.engOptions[n];
801 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802 cps->isUCI = appData.isUCI[n]; /* [AS] */
803 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
805 if (appData.protocolVersion[n] > PROTOVER
806 || appData.protocolVersion[n] < 1)
811 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812 appData.protocolVersion[n]);
813 if( (len > MSG_SIZ) && appData.debugMode )
814 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
816 DisplayFatalError(buf, 0, 2);
820 cps->protocolVersion = appData.protocolVersion[n];
823 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
826 ChessProgramState *savCps;
832 if(WaitForEngine(savCps, LoadEngine)) return;
833 CommonEngineInit(); // recalculate time odds
834 if(gameInfo.variant != StringToVariant(appData.variant)) {
835 // we changed variant when loading the engine; this forces us to reset
836 Reset(TRUE, savCps != &first);
837 EditGameEvent(); // for consistency with other path, as Reset changes mode
839 InitChessProgram(savCps, FALSE);
840 SendToProgram("force\n", savCps);
841 DisplayMessage("", "");
842 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849 ReplaceEngine(ChessProgramState *cps, int n)
853 appData.noChessProgram = FALSE;
854 appData.clockMode = TRUE;
856 if(n) return; // only startup first engine immediately; second can wait
857 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
864 void Load(ChessProgramState *cps, int i)
866 char *p, *q, buf[MSG_SIZ];
867 if(engineLine[0]) { // an engine was selected from the combo box
868 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
869 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
870 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
871 ParseArgsFromString(buf);
873 ReplaceEngine(cps, i);
877 while(q = strchr(p, SLASH)) p = q+1;
878 if(*p== NULLCHAR) return;
879 appData.chessProgram[i] = strdup(p);
880 if(engineDir[0] != NULLCHAR)
881 appData.directory[i] = engineDir;
882 else if(p != engineName) { // derive directory from engine path, when not given
884 appData.directory[i] = strdup(engineName);
886 } else appData.directory[i] = ".";
887 appData.isUCI[i] = isUCI;
888 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
889 appData.hasOwnBookUCI[i] = hasBook;
892 q = firstChessProgramNames;
893 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
894 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
895 v1 ? " -firstProtocolVersion 1" : "",
896 hasBook ? "" : " -fNoOwnBookUCI",
897 isUCI ? " -fUCI" : "",
898 storeVariant ? " -variant " : "",
899 storeVariant ? VariantName(gameInfo.variant) : "");
900 fprintf(debugFP, "new line: %s", buf);
901 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
902 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
905 ReplaceEngine(cps, i);
911 int matched, min, sec;
913 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
914 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
916 GetTimeMark(&programStartTime);
917 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
918 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
921 programStats.ok_to_send = 1;
922 programStats.seen_stat = 0;
925 * Initialize game list
931 * Internet chess server status
933 if (appData.icsActive) {
934 appData.matchMode = FALSE;
935 appData.matchGames = 0;
937 appData.noChessProgram = !appData.zippyPlay;
939 appData.zippyPlay = FALSE;
940 appData.zippyTalk = FALSE;
941 appData.noChessProgram = TRUE;
943 if (*appData.icsHelper != NULLCHAR) {
944 appData.useTelnet = TRUE;
945 appData.telnetProgram = appData.icsHelper;
948 appData.zippyTalk = appData.zippyPlay = FALSE;
951 /* [AS] Initialize pv info list [HGM] and game state */
955 for( i=0; i<=framePtr; i++ ) {
956 pvInfoList[i].depth = -1;
957 boards[i][EP_STATUS] = EP_NONE;
958 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
963 * Parse timeControl resource
965 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
966 appData.movesPerSession)) {
968 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
969 DisplayFatalError(buf, 0, 2);
973 * Parse searchTime resource
975 if (*appData.searchTime != NULLCHAR) {
976 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978 searchTime = min * 60;
979 } else if (matched == 2) {
980 searchTime = min * 60 + sec;
983 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
984 DisplayFatalError(buf, 0, 2);
988 /* [AS] Adjudication threshold */
989 adjudicateLossThreshold = appData.adjudicateLossThreshold;
991 InitEngine(&first, 0);
992 InitEngine(&second, 1);
995 if (appData.icsActive) {
996 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
997 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
998 appData.clockMode = FALSE;
999 first.sendTime = second.sendTime = 0;
1003 /* Override some settings from environment variables, for backward
1004 compatibility. Unfortunately it's not feasible to have the env
1005 vars just set defaults, at least in xboard. Ugh.
1007 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1012 if (!appData.icsActive) {
1016 /* Check for variants that are supported only in ICS mode,
1017 or not at all. Some that are accepted here nevertheless
1018 have bugs; see comments below.
1020 VariantClass variant = StringToVariant(appData.variant);
1022 case VariantBughouse: /* need four players and two boards */
1023 case VariantKriegspiel: /* need to hide pieces and move details */
1024 /* case VariantFischeRandom: (Fabien: moved below) */
1025 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1026 if( (len > MSG_SIZ) && appData.debugMode )
1027 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1029 DisplayFatalError(buf, 0, 2);
1032 case VariantUnknown:
1033 case VariantLoadable:
1043 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1044 if( (len > MSG_SIZ) && appData.debugMode )
1045 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1047 DisplayFatalError(buf, 0, 2);
1050 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1051 case VariantFairy: /* [HGM] TestLegality definitely off! */
1052 case VariantGothic: /* [HGM] should work */
1053 case VariantCapablanca: /* [HGM] should work */
1054 case VariantCourier: /* [HGM] initial forced moves not implemented */
1055 case VariantShogi: /* [HGM] could still mate with pawn drop */
1056 case VariantKnightmate: /* [HGM] should work */
1057 case VariantCylinder: /* [HGM] untested */
1058 case VariantFalcon: /* [HGM] untested */
1059 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1060 offboard interposition not understood */
1061 case VariantNormal: /* definitely works! */
1062 case VariantWildCastle: /* pieces not automatically shuffled */
1063 case VariantNoCastle: /* pieces not automatically shuffled */
1064 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1065 case VariantLosers: /* should work except for win condition,
1066 and doesn't know captures are mandatory */
1067 case VariantSuicide: /* should work except for win condition,
1068 and doesn't know captures are mandatory */
1069 case VariantGiveaway: /* should work except for win condition,
1070 and doesn't know captures are mandatory */
1071 case VariantTwoKings: /* should work */
1072 case VariantAtomic: /* should work except for win condition */
1073 case Variant3Check: /* should work except for win condition */
1074 case VariantShatranj: /* should work except for all win conditions */
1075 case VariantMakruk: /* should work except for daw countdown */
1076 case VariantBerolina: /* might work if TestLegality is off */
1077 case VariantCapaRandom: /* should work */
1078 case VariantJanus: /* should work */
1079 case VariantSuper: /* experimental */
1080 case VariantGreat: /* experimental, requires legality testing to be off */
1081 case VariantSChess: /* S-Chess, should work */
1082 case VariantSpartan: /* should work */
1089 int NextIntegerFromString( char ** str, long * value )
1094 while( *s == ' ' || *s == '\t' ) {
1100 if( *s >= '0' && *s <= '9' ) {
1101 while( *s >= '0' && *s <= '9' ) {
1102 *value = *value * 10 + (*s - '0');
1114 int NextTimeControlFromString( char ** str, long * value )
1117 int result = NextIntegerFromString( str, &temp );
1120 *value = temp * 60; /* Minutes */
1121 if( **str == ':' ) {
1123 result = NextIntegerFromString( str, &temp );
1124 *value += temp; /* Seconds */
1131 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1132 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1133 int result = -1, type = 0; long temp, temp2;
1135 if(**str != ':') return -1; // old params remain in force!
1137 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1138 if( NextIntegerFromString( str, &temp ) ) return -1;
1139 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1142 /* time only: incremental or sudden-death time control */
1143 if(**str == '+') { /* increment follows; read it */
1145 if(**str == '!') type = *(*str)++; // Bronstein TC
1146 if(result = NextIntegerFromString( str, &temp2)) return -1;
1147 *inc = temp2 * 1000;
1148 if(**str == '.') { // read fraction of increment
1149 char *start = ++(*str);
1150 if(result = NextIntegerFromString( str, &temp2)) return -1;
1152 while(start++ < *str) temp2 /= 10;
1156 *moves = 0; *tc = temp * 1000; *incType = type;
1160 (*str)++; /* classical time control */
1161 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1172 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1173 { /* [HGM] get time to add from the multi-session time-control string */
1174 int incType, moves=1; /* kludge to force reading of first session */
1175 long time, increment;
1178 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1179 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1181 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1182 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1183 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1184 if(movenr == -1) return time; /* last move before new session */
1185 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1186 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1187 if(!moves) return increment; /* current session is incremental */
1188 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1189 } while(movenr >= -1); /* try again for next session */
1191 return 0; // no new time quota on this move
1195 ParseTimeControl(tc, ti, mps)
1202 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1205 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1206 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1207 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1211 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1213 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1216 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1218 snprintf(buf, MSG_SIZ, ":%s", mytc);
1220 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1222 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1227 /* Parse second time control */
1230 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1238 timeControl_2 = tc2 * 1000;
1248 timeControl = tc1 * 1000;
1251 timeIncrement = ti * 1000; /* convert to ms */
1252 movesPerSession = 0;
1255 movesPerSession = mps;
1263 if (appData.debugMode) {
1264 fprintf(debugFP, "%s\n", programVersion);
1267 set_cont_sequence(appData.wrapContSeq);
1268 if (appData.matchGames > 0) {
1269 appData.matchMode = TRUE;
1270 } else if (appData.matchMode) {
1271 appData.matchGames = 1;
1273 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1274 appData.matchGames = appData.sameColorGames;
1275 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1276 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1277 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1280 if (appData.noChessProgram || first.protocolVersion == 1) {
1283 /* kludge: allow timeout for initial "feature" commands */
1285 DisplayMessage("", _("Starting chess program"));
1286 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1291 CalculateIndex(int index, int gameNr)
1292 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1294 if(index > 0) return index; // fixed nmber
1295 if(index == 0) return 1;
1296 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1297 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1302 LoadGameOrPosition(int gameNr)
1303 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1304 if (*appData.loadGameFile != NULLCHAR) {
1305 if (!LoadGameFromFile(appData.loadGameFile,
1306 CalculateIndex(appData.loadGameIndex, gameNr),
1307 appData.loadGameFile, FALSE)) {
1308 DisplayFatalError(_("Bad game file"), 0, 1);
1311 } else if (*appData.loadPositionFile != NULLCHAR) {
1312 if (!LoadPositionFromFile(appData.loadPositionFile,
1313 CalculateIndex(appData.loadPositionIndex, gameNr),
1314 appData.loadPositionFile)) {
1315 DisplayFatalError(_("Bad position file"), 0, 1);
1323 ReserveGame(int gameNr, char resChar)
1325 FILE *tf = fopen(appData.tourneyFile, "r+");
1326 char *p, *q, c, buf[MSG_SIZ];
1327 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1328 safeStrCpy(buf, lastMsg, MSG_SIZ);
1329 DisplayMessage(_("Pick new game"), "");
1330 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1331 ParseArgsFromFile(tf);
1332 p = q = appData.results;
1333 if(appData.debugMode) {
1334 char *r = appData.participants;
1335 fprintf(debugFP, "results = '%s'\n", p);
1336 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1337 fprintf(debugFP, "\n");
1339 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1341 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1342 safeStrCpy(q, p, strlen(p) + 2);
1343 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1344 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1345 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1346 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1349 fseek(tf, -(strlen(p)+4), SEEK_END);
1351 if(c != '"') // depending on DOS or Unix line endings we can be one off
1352 fseek(tf, -(strlen(p)+2), SEEK_END);
1353 else fseek(tf, -(strlen(p)+3), SEEK_END);
1354 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1355 DisplayMessage(buf, "");
1356 free(p); appData.results = q;
1357 if(nextGame <= appData.matchGames && resChar != ' ' &&
1358 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1359 UnloadEngine(&first); // next game belongs to other pairing;
1360 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1365 MatchEvent(int mode)
1366 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1368 if(matchMode) { // already in match mode: switch it off
1369 appData.matchGames = matchGame; // kludge to let match terminate after next game.
1370 ModeHighlight(); // kludgey way to remove checkmark...
1373 if(gameMode != BeginningOfGame) {
1374 DisplayError(_("You can only start a match from the initial position."), 0);
1377 appData.matchGames = appData.defaultMatchGames;
1378 /* Set up machine vs. machine match */
1380 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1381 if(appData.tourneyFile[0]) {
1383 if(nextGame > appData.matchGames) {
1385 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1386 DisplayError(buf, 0);
1387 appData.tourneyFile[0] = 0;
1391 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1392 DisplayFatalError(_("Can't have a match with no chess programs"),
1397 matchGame = roundNr = 1;
1398 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1403 InitBackEnd3 P((void))
1405 GameMode initialMode;
1409 InitChessProgram(&first, startedFromSetupPosition);
1411 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1412 free(programVersion);
1413 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1414 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1417 if (appData.icsActive) {
1419 /* [DM] Make a console window if needed [HGM] merged ifs */
1425 if (*appData.icsCommPort != NULLCHAR)
1426 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1427 appData.icsCommPort);
1429 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1430 appData.icsHost, appData.icsPort);
1432 if( (len > MSG_SIZ) && appData.debugMode )
1433 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1435 DisplayFatalError(buf, err, 1);
1440 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1442 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1443 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1444 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1445 } else if (appData.noChessProgram) {
1451 if (*appData.cmailGameName != NULLCHAR) {
1453 OpenLoopback(&cmailPR);
1455 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1459 DisplayMessage("", "");
1460 if (StrCaseCmp(appData.initialMode, "") == 0) {
1461 initialMode = BeginningOfGame;
1462 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1463 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1464 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1465 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1468 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1469 initialMode = TwoMachinesPlay;
1470 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1471 initialMode = AnalyzeFile;
1472 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1473 initialMode = AnalyzeMode;
1474 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1475 initialMode = MachinePlaysWhite;
1476 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1477 initialMode = MachinePlaysBlack;
1478 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1479 initialMode = EditGame;
1480 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1481 initialMode = EditPosition;
1482 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1483 initialMode = Training;
1485 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1486 if( (len > MSG_SIZ) && appData.debugMode )
1487 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1489 DisplayFatalError(buf, 0, 2);
1493 if (appData.matchMode) {
1494 if(appData.tourneyFile[0]) { // start tourney from command line
1496 if(f = fopen(appData.tourneyFile, "r")) {
1497 ParseArgsFromFile(f); // make sure tourney parmeters re known
1499 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1502 } else if (*appData.cmailGameName != NULLCHAR) {
1503 /* Set up cmail mode */
1504 ReloadCmailMsgEvent(TRUE);
1506 /* Set up other modes */
1507 if (initialMode == AnalyzeFile) {
1508 if (*appData.loadGameFile == NULLCHAR) {
1509 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1513 if (*appData.loadGameFile != NULLCHAR) {
1514 (void) LoadGameFromFile(appData.loadGameFile,
1515 appData.loadGameIndex,
1516 appData.loadGameFile, TRUE);
1517 } else if (*appData.loadPositionFile != NULLCHAR) {
1518 (void) LoadPositionFromFile(appData.loadPositionFile,
1519 appData.loadPositionIndex,
1520 appData.loadPositionFile);
1521 /* [HGM] try to make self-starting even after FEN load */
1522 /* to allow automatic setup of fairy variants with wtm */
1523 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1524 gameMode = BeginningOfGame;
1525 setboardSpoiledMachineBlack = 1;
1527 /* [HGM] loadPos: make that every new game uses the setup */
1528 /* from file as long as we do not switch variant */
1529 if(!blackPlaysFirst) {
1530 startedFromPositionFile = TRUE;
1531 CopyBoard(filePosition, boards[0]);
1534 if (initialMode == AnalyzeMode) {
1535 if (appData.noChessProgram) {
1536 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1539 if (appData.icsActive) {
1540 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1544 } else if (initialMode == AnalyzeFile) {
1545 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1546 ShowThinkingEvent();
1548 AnalysisPeriodicEvent(1);
1549 } else if (initialMode == MachinePlaysWhite) {
1550 if (appData.noChessProgram) {
1551 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1555 if (appData.icsActive) {
1556 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1560 MachineWhiteEvent();
1561 } else if (initialMode == MachinePlaysBlack) {
1562 if (appData.noChessProgram) {
1563 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1567 if (appData.icsActive) {
1568 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1572 MachineBlackEvent();
1573 } else if (initialMode == TwoMachinesPlay) {
1574 if (appData.noChessProgram) {
1575 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1579 if (appData.icsActive) {
1580 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1585 } else if (initialMode == EditGame) {
1587 } else if (initialMode == EditPosition) {
1588 EditPositionEvent();
1589 } else if (initialMode == Training) {
1590 if (*appData.loadGameFile == NULLCHAR) {
1591 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1600 * Establish will establish a contact to a remote host.port.
1601 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1602 * used to talk to the host.
1603 * Returns 0 if okay, error code if not.
1610 if (*appData.icsCommPort != NULLCHAR) {
1611 /* Talk to the host through a serial comm port */
1612 return OpenCommPort(appData.icsCommPort, &icsPR);
1614 } else if (*appData.gateway != NULLCHAR) {
1615 if (*appData.remoteShell == NULLCHAR) {
1616 /* Use the rcmd protocol to run telnet program on a gateway host */
1617 snprintf(buf, sizeof(buf), "%s %s %s",
1618 appData.telnetProgram, appData.icsHost, appData.icsPort);
1619 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1622 /* Use the rsh program to run telnet program on a gateway host */
1623 if (*appData.remoteUser == NULLCHAR) {
1624 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1625 appData.gateway, appData.telnetProgram,
1626 appData.icsHost, appData.icsPort);
1628 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1629 appData.remoteShell, appData.gateway,
1630 appData.remoteUser, appData.telnetProgram,
1631 appData.icsHost, appData.icsPort);
1633 return StartChildProcess(buf, "", &icsPR);
1636 } else if (appData.useTelnet) {
1637 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1640 /* TCP socket interface differs somewhat between
1641 Unix and NT; handle details in the front end.
1643 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1647 void EscapeExpand(char *p, char *q)
1648 { // [HGM] initstring: routine to shape up string arguments
1649 while(*p++ = *q++) if(p[-1] == '\\')
1651 case 'n': p[-1] = '\n'; break;
1652 case 'r': p[-1] = '\r'; break;
1653 case 't': p[-1] = '\t'; break;
1654 case '\\': p[-1] = '\\'; break;
1655 case 0: *p = 0; return;
1656 default: p[-1] = q[-1]; break;
1661 show_bytes(fp, buf, count)
1667 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1668 fprintf(fp, "\\%03o", *buf & 0xff);
1677 /* Returns an errno value */
1679 OutputMaybeTelnet(pr, message, count, outError)
1685 char buf[8192], *p, *q, *buflim;
1686 int left, newcount, outcount;
1688 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1689 *appData.gateway != NULLCHAR) {
1690 if (appData.debugMode) {
1691 fprintf(debugFP, ">ICS: ");
1692 show_bytes(debugFP, message, count);
1693 fprintf(debugFP, "\n");
1695 return OutputToProcess(pr, message, count, outError);
1698 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1705 if (appData.debugMode) {
1706 fprintf(debugFP, ">ICS: ");
1707 show_bytes(debugFP, buf, newcount);
1708 fprintf(debugFP, "\n");
1710 outcount = OutputToProcess(pr, buf, newcount, outError);
1711 if (outcount < newcount) return -1; /* to be sure */
1718 } else if (((unsigned char) *p) == TN_IAC) {
1719 *q++ = (char) TN_IAC;
1726 if (appData.debugMode) {
1727 fprintf(debugFP, ">ICS: ");
1728 show_bytes(debugFP, buf, newcount);
1729 fprintf(debugFP, "\n");
1731 outcount = OutputToProcess(pr, buf, newcount, outError);
1732 if (outcount < newcount) return -1; /* to be sure */
1737 read_from_player(isr, closure, message, count, error)
1744 int outError, outCount;
1745 static int gotEof = 0;
1747 /* Pass data read from player on to ICS */
1750 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1751 if (outCount < count) {
1752 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1754 } else if (count < 0) {
1755 RemoveInputSource(isr);
1756 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1757 } else if (gotEof++ > 0) {
1758 RemoveInputSource(isr);
1759 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1765 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1766 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1767 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1768 SendToICS("date\n");
1769 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1772 /* added routine for printf style output to ics */
1773 void ics_printf(char *format, ...)
1775 char buffer[MSG_SIZ];
1778 va_start(args, format);
1779 vsnprintf(buffer, sizeof(buffer), format, args);
1780 buffer[sizeof(buffer)-1] = '\0';
1789 int count, outCount, outError;
1791 if (icsPR == NULL) return;
1794 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1795 if (outCount < count) {
1796 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1800 /* This is used for sending logon scripts to the ICS. Sending
1801 without a delay causes problems when using timestamp on ICC
1802 (at least on my machine). */
1804 SendToICSDelayed(s,msdelay)
1808 int count, outCount, outError;
1810 if (icsPR == NULL) return;
1813 if (appData.debugMode) {
1814 fprintf(debugFP, ">ICS: ");
1815 show_bytes(debugFP, s, count);
1816 fprintf(debugFP, "\n");
1818 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1820 if (outCount < count) {
1821 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1826 /* Remove all highlighting escape sequences in s
1827 Also deletes any suffix starting with '('
1830 StripHighlightAndTitle(s)
1833 static char retbuf[MSG_SIZ];
1836 while (*s != NULLCHAR) {
1837 while (*s == '\033') {
1838 while (*s != NULLCHAR && !isalpha(*s)) s++;
1839 if (*s != NULLCHAR) s++;
1841 while (*s != NULLCHAR && *s != '\033') {
1842 if (*s == '(' || *s == '[') {
1853 /* Remove all highlighting escape sequences in s */
1858 static char retbuf[MSG_SIZ];
1861 while (*s != NULLCHAR) {
1862 while (*s == '\033') {
1863 while (*s != NULLCHAR && !isalpha(*s)) s++;
1864 if (*s != NULLCHAR) s++;
1866 while (*s != NULLCHAR && *s != '\033') {
1874 char *variantNames[] = VARIANT_NAMES;
1879 return variantNames[v];
1883 /* Identify a variant from the strings the chess servers use or the
1884 PGN Variant tag names we use. */
1891 VariantClass v = VariantNormal;
1892 int i, found = FALSE;
1898 /* [HGM] skip over optional board-size prefixes */
1899 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1900 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1901 while( *e++ != '_');
1904 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1908 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1909 if (StrCaseStr(e, variantNames[i])) {
1910 v = (VariantClass) i;
1917 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1918 || StrCaseStr(e, "wild/fr")
1919 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1920 v = VariantFischeRandom;
1921 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1922 (i = 1, p = StrCaseStr(e, "w"))) {
1924 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1931 case 0: /* FICS only, actually */
1933 /* Castling legal even if K starts on d-file */
1934 v = VariantWildCastle;
1939 /* Castling illegal even if K & R happen to start in
1940 normal positions. */
1941 v = VariantNoCastle;
1954 /* Castling legal iff K & R start in normal positions */
1960 /* Special wilds for position setup; unclear what to do here */
1961 v = VariantLoadable;
1964 /* Bizarre ICC game */
1965 v = VariantTwoKings;
1968 v = VariantKriegspiel;
1974 v = VariantFischeRandom;
1977 v = VariantCrazyhouse;
1980 v = VariantBughouse;
1986 /* Not quite the same as FICS suicide! */
1987 v = VariantGiveaway;
1993 v = VariantShatranj;
1996 /* Temporary names for future ICC types. The name *will* change in
1997 the next xboard/WinBoard release after ICC defines it. */
2035 v = VariantCapablanca;
2038 v = VariantKnightmate;
2044 v = VariantCylinder;
2050 v = VariantCapaRandom;
2053 v = VariantBerolina;
2065 /* Found "wild" or "w" in the string but no number;
2066 must assume it's normal chess. */
2070 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2071 if( (len > MSG_SIZ) && appData.debugMode )
2072 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2074 DisplayError(buf, 0);
2080 if (appData.debugMode) {
2081 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2082 e, wnum, VariantName(v));
2087 static int leftover_start = 0, leftover_len = 0;
2088 char star_match[STAR_MATCH_N][MSG_SIZ];
2090 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2091 advance *index beyond it, and set leftover_start to the new value of
2092 *index; else return FALSE. If pattern contains the character '*', it
2093 matches any sequence of characters not containing '\r', '\n', or the
2094 character following the '*' (if any), and the matched sequence(s) are
2095 copied into star_match.
2098 looking_at(buf, index, pattern)
2103 char *bufp = &buf[*index], *patternp = pattern;
2105 char *matchp = star_match[0];
2108 if (*patternp == NULLCHAR) {
2109 *index = leftover_start = bufp - buf;
2113 if (*bufp == NULLCHAR) return FALSE;
2114 if (*patternp == '*') {
2115 if (*bufp == *(patternp + 1)) {
2117 matchp = star_match[++star_count];
2121 } else if (*bufp == '\n' || *bufp == '\r') {
2123 if (*patternp == NULLCHAR)
2128 *matchp++ = *bufp++;
2132 if (*patternp != *bufp) return FALSE;
2139 SendToPlayer(data, length)
2143 int error, outCount;
2144 outCount = OutputToProcess(NoProc, data, length, &error);
2145 if (outCount < length) {
2146 DisplayFatalError(_("Error writing to display"), error, 1);
2151 PackHolding(packed, holding)
2163 switch (runlength) {
2174 sprintf(q, "%d", runlength);
2186 /* Telnet protocol requests from the front end */
2188 TelnetRequest(ddww, option)
2189 unsigned char ddww, option;
2191 unsigned char msg[3];
2192 int outCount, outError;
2194 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2196 if (appData.debugMode) {
2197 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2213 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2222 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2225 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2230 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2232 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2239 if (!appData.icsActive) return;
2240 TelnetRequest(TN_DO, TN_ECHO);
2246 if (!appData.icsActive) return;
2247 TelnetRequest(TN_DONT, TN_ECHO);
2251 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2253 /* put the holdings sent to us by the server on the board holdings area */
2254 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2258 if(gameInfo.holdingsWidth < 2) return;
2259 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2260 return; // prevent overwriting by pre-board holdings
2262 if( (int)lowestPiece >= BlackPawn ) {
2265 holdingsStartRow = BOARD_HEIGHT-1;
2268 holdingsColumn = BOARD_WIDTH-1;
2269 countsColumn = BOARD_WIDTH-2;
2270 holdingsStartRow = 0;
2274 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2275 board[i][holdingsColumn] = EmptySquare;
2276 board[i][countsColumn] = (ChessSquare) 0;
2278 while( (p=*holdings++) != NULLCHAR ) {
2279 piece = CharToPiece( ToUpper(p) );
2280 if(piece == EmptySquare) continue;
2281 /*j = (int) piece - (int) WhitePawn;*/
2282 j = PieceToNumber(piece);
2283 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2284 if(j < 0) continue; /* should not happen */
2285 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2286 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2287 board[holdingsStartRow+j*direction][countsColumn]++;
2293 VariantSwitch(Board board, VariantClass newVariant)
2295 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2296 static Board oldBoard;
2298 startedFromPositionFile = FALSE;
2299 if(gameInfo.variant == newVariant) return;
2301 /* [HGM] This routine is called each time an assignment is made to
2302 * gameInfo.variant during a game, to make sure the board sizes
2303 * are set to match the new variant. If that means adding or deleting
2304 * holdings, we shift the playing board accordingly
2305 * This kludge is needed because in ICS observe mode, we get boards
2306 * of an ongoing game without knowing the variant, and learn about the
2307 * latter only later. This can be because of the move list we requested,
2308 * in which case the game history is refilled from the beginning anyway,
2309 * but also when receiving holdings of a crazyhouse game. In the latter
2310 * case we want to add those holdings to the already received position.
2314 if (appData.debugMode) {
2315 fprintf(debugFP, "Switch board from %s to %s\n",
2316 VariantName(gameInfo.variant), VariantName(newVariant));
2317 setbuf(debugFP, NULL);
2319 shuffleOpenings = 0; /* [HGM] shuffle */
2320 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2324 newWidth = 9; newHeight = 9;
2325 gameInfo.holdingsSize = 7;
2326 case VariantBughouse:
2327 case VariantCrazyhouse:
2328 newHoldingsWidth = 2; break;
2332 newHoldingsWidth = 2;
2333 gameInfo.holdingsSize = 8;
2336 case VariantCapablanca:
2337 case VariantCapaRandom:
2340 newHoldingsWidth = gameInfo.holdingsSize = 0;
2343 if(newWidth != gameInfo.boardWidth ||
2344 newHeight != gameInfo.boardHeight ||
2345 newHoldingsWidth != gameInfo.holdingsWidth ) {
2347 /* shift position to new playing area, if needed */
2348 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2349 for(i=0; i<BOARD_HEIGHT; i++)
2350 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2351 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2353 for(i=0; i<newHeight; i++) {
2354 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2355 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2357 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2358 for(i=0; i<BOARD_HEIGHT; i++)
2359 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2360 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2363 gameInfo.boardWidth = newWidth;
2364 gameInfo.boardHeight = newHeight;
2365 gameInfo.holdingsWidth = newHoldingsWidth;
2366 gameInfo.variant = newVariant;
2367 InitDrawingSizes(-2, 0);
2368 } else gameInfo.variant = newVariant;
2369 CopyBoard(oldBoard, board); // remember correctly formatted board
2370 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2371 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2374 static int loggedOn = FALSE;
2376 /*-- Game start info cache: --*/
2378 char gs_kind[MSG_SIZ];
2379 static char player1Name[128] = "";
2380 static char player2Name[128] = "";
2381 static char cont_seq[] = "\n\\ ";
2382 static int player1Rating = -1;
2383 static int player2Rating = -1;
2384 /*----------------------------*/
2386 ColorClass curColor = ColorNormal;
2387 int suppressKibitz = 0;
2390 Boolean soughtPending = FALSE;
2391 Boolean seekGraphUp;
2392 #define MAX_SEEK_ADS 200
2394 char *seekAdList[MAX_SEEK_ADS];
2395 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2396 float tcList[MAX_SEEK_ADS];
2397 char colorList[MAX_SEEK_ADS];
2398 int nrOfSeekAds = 0;
2399 int minRating = 1010, maxRating = 2800;
2400 int hMargin = 10, vMargin = 20, h, w;
2401 extern int squareSize, lineGap;
2406 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2407 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2408 if(r < minRating+100 && r >=0 ) r = minRating+100;
2409 if(r > maxRating) r = maxRating;
2410 if(tc < 1.) tc = 1.;
2411 if(tc > 95.) tc = 95.;
2412 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2413 y = ((double)r - minRating)/(maxRating - minRating)
2414 * (h-vMargin-squareSize/8-1) + vMargin;
2415 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2416 if(strstr(seekAdList[i], " u ")) color = 1;
2417 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2418 !strstr(seekAdList[i], "bullet") &&
2419 !strstr(seekAdList[i], "blitz") &&
2420 !strstr(seekAdList[i], "standard") ) color = 2;
2421 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2422 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2426 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2428 char buf[MSG_SIZ], *ext = "";
2429 VariantClass v = StringToVariant(type);
2430 if(strstr(type, "wild")) {
2431 ext = type + 4; // append wild number
2432 if(v == VariantFischeRandom) type = "chess960"; else
2433 if(v == VariantLoadable) type = "setup"; else
2434 type = VariantName(v);
2436 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2437 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2438 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2439 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2440 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2441 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2442 seekNrList[nrOfSeekAds] = nr;
2443 zList[nrOfSeekAds] = 0;
2444 seekAdList[nrOfSeekAds++] = StrSave(buf);
2445 if(plot) PlotSeekAd(nrOfSeekAds-1);
2452 int x = xList[i], y = yList[i], d=squareSize/4, k;
2453 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2454 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2455 // now replot every dot that overlapped
2456 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2457 int xx = xList[k], yy = yList[k];
2458 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2459 DrawSeekDot(xx, yy, colorList[k]);
2464 RemoveSeekAd(int nr)
2467 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2469 if(seekAdList[i]) free(seekAdList[i]);
2470 seekAdList[i] = seekAdList[--nrOfSeekAds];
2471 seekNrList[i] = seekNrList[nrOfSeekAds];
2472 ratingList[i] = ratingList[nrOfSeekAds];
2473 colorList[i] = colorList[nrOfSeekAds];
2474 tcList[i] = tcList[nrOfSeekAds];
2475 xList[i] = xList[nrOfSeekAds];
2476 yList[i] = yList[nrOfSeekAds];
2477 zList[i] = zList[nrOfSeekAds];
2478 seekAdList[nrOfSeekAds] = NULL;
2484 MatchSoughtLine(char *line)
2486 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2487 int nr, base, inc, u=0; char dummy;
2489 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2490 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2492 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2493 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2494 // match: compact and save the line
2495 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2505 if(!seekGraphUp) return FALSE;
2506 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2507 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2509 DrawSeekBackground(0, 0, w, h);
2510 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2511 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2512 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2513 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2515 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2518 snprintf(buf, MSG_SIZ, "%d", i);
2519 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2522 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2523 for(i=1; i<100; i+=(i<10?1:5)) {
2524 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2525 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2526 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2528 snprintf(buf, MSG_SIZ, "%d", i);
2529 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2532 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2536 int SeekGraphClick(ClickType click, int x, int y, int moving)
2538 static int lastDown = 0, displayed = 0, lastSecond;
2539 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2540 if(click == Release || moving) return FALSE;
2542 soughtPending = TRUE;
2543 SendToICS(ics_prefix);
2544 SendToICS("sought\n"); // should this be "sought all"?
2545 } else { // issue challenge based on clicked ad
2546 int dist = 10000; int i, closest = 0, second = 0;
2547 for(i=0; i<nrOfSeekAds; i++) {
2548 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2549 if(d < dist) { dist = d; closest = i; }
2550 second += (d - zList[i] < 120); // count in-range ads
2551 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2555 second = (second > 1);
2556 if(displayed != closest || second != lastSecond) {
2557 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2558 lastSecond = second; displayed = closest;
2560 if(click == Press) {
2561 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2564 } // on press 'hit', only show info
2565 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2566 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2567 SendToICS(ics_prefix);
2569 return TRUE; // let incoming board of started game pop down the graph
2570 } else if(click == Release) { // release 'miss' is ignored
2571 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2572 if(moving == 2) { // right up-click
2573 nrOfSeekAds = 0; // refresh graph
2574 soughtPending = TRUE;
2575 SendToICS(ics_prefix);
2576 SendToICS("sought\n"); // should this be "sought all"?
2579 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2580 // press miss or release hit 'pop down' seek graph
2581 seekGraphUp = FALSE;
2582 DrawPosition(TRUE, NULL);
2588 read_from_ics(isr, closure, data, count, error)
2595 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2596 #define STARTED_NONE 0
2597 #define STARTED_MOVES 1
2598 #define STARTED_BOARD 2
2599 #define STARTED_OBSERVE 3
2600 #define STARTED_HOLDINGS 4
2601 #define STARTED_CHATTER 5
2602 #define STARTED_COMMENT 6
2603 #define STARTED_MOVES_NOHIDE 7
2605 static int started = STARTED_NONE;
2606 static char parse[20000];
2607 static int parse_pos = 0;
2608 static char buf[BUF_SIZE + 1];
2609 static int firstTime = TRUE, intfSet = FALSE;
2610 static ColorClass prevColor = ColorNormal;
2611 static int savingComment = FALSE;
2612 static int cmatch = 0; // continuation sequence match
2619 int backup; /* [DM] For zippy color lines */
2621 char talker[MSG_SIZ]; // [HGM] chat
2624 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2626 if (appData.debugMode) {
2628 fprintf(debugFP, "<ICS: ");
2629 show_bytes(debugFP, data, count);
2630 fprintf(debugFP, "\n");
2634 if (appData.debugMode) { int f = forwardMostMove;
2635 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2636 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2637 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2640 /* If last read ended with a partial line that we couldn't parse,
2641 prepend it to the new read and try again. */
2642 if (leftover_len > 0) {
2643 for (i=0; i<leftover_len; i++)
2644 buf[i] = buf[leftover_start + i];
2647 /* copy new characters into the buffer */
2648 bp = buf + leftover_len;
2649 buf_len=leftover_len;
2650 for (i=0; i<count; i++)
2653 if (data[i] == '\r')
2656 // join lines split by ICS?
2657 if (!appData.noJoin)
2660 Joining just consists of finding matches against the
2661 continuation sequence, and discarding that sequence
2662 if found instead of copying it. So, until a match
2663 fails, there's nothing to do since it might be the
2664 complete sequence, and thus, something we don't want
2667 if (data[i] == cont_seq[cmatch])
2670 if (cmatch == strlen(cont_seq))
2672 cmatch = 0; // complete match. just reset the counter
2675 it's possible for the ICS to not include the space
2676 at the end of the last word, making our [correct]
2677 join operation fuse two separate words. the server
2678 does this when the space occurs at the width setting.
2680 if (!buf_len || buf[buf_len-1] != ' ')
2691 match failed, so we have to copy what matched before
2692 falling through and copying this character. In reality,
2693 this will only ever be just the newline character, but
2694 it doesn't hurt to be precise.
2696 strncpy(bp, cont_seq, cmatch);
2708 buf[buf_len] = NULLCHAR;
2709 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2714 while (i < buf_len) {
2715 /* Deal with part of the TELNET option negotiation
2716 protocol. We refuse to do anything beyond the
2717 defaults, except that we allow the WILL ECHO option,
2718 which ICS uses to turn off password echoing when we are
2719 directly connected to it. We reject this option
2720 if localLineEditing mode is on (always on in xboard)
2721 and we are talking to port 23, which might be a real
2722 telnet server that will try to keep WILL ECHO on permanently.
2724 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2725 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2726 unsigned char option;
2728 switch ((unsigned char) buf[++i]) {
2730 if (appData.debugMode)
2731 fprintf(debugFP, "\n<WILL ");
2732 switch (option = (unsigned char) buf[++i]) {
2734 if (appData.debugMode)
2735 fprintf(debugFP, "ECHO ");
2736 /* Reply only if this is a change, according
2737 to the protocol rules. */
2738 if (remoteEchoOption) break;
2739 if (appData.localLineEditing &&
2740 atoi(appData.icsPort) == TN_PORT) {
2741 TelnetRequest(TN_DONT, TN_ECHO);
2744 TelnetRequest(TN_DO, TN_ECHO);
2745 remoteEchoOption = TRUE;
2749 if (appData.debugMode)
2750 fprintf(debugFP, "%d ", option);
2751 /* Whatever this is, we don't want it. */
2752 TelnetRequest(TN_DONT, option);
2757 if (appData.debugMode)
2758 fprintf(debugFP, "\n<WONT ");
2759 switch (option = (unsigned char) buf[++i]) {
2761 if (appData.debugMode)
2762 fprintf(debugFP, "ECHO ");
2763 /* Reply only if this is a change, according
2764 to the protocol rules. */
2765 if (!remoteEchoOption) break;
2767 TelnetRequest(TN_DONT, TN_ECHO);
2768 remoteEchoOption = FALSE;
2771 if (appData.debugMode)
2772 fprintf(debugFP, "%d ", (unsigned char) option);
2773 /* Whatever this is, it must already be turned
2774 off, because we never agree to turn on
2775 anything non-default, so according to the
2776 protocol rules, we don't reply. */
2781 if (appData.debugMode)
2782 fprintf(debugFP, "\n<DO ");
2783 switch (option = (unsigned char) buf[++i]) {
2785 /* Whatever this is, we refuse to do it. */
2786 if (appData.debugMode)
2787 fprintf(debugFP, "%d ", option);
2788 TelnetRequest(TN_WONT, option);
2793 if (appData.debugMode)
2794 fprintf(debugFP, "\n<DONT ");
2795 switch (option = (unsigned char) buf[++i]) {
2797 if (appData.debugMode)
2798 fprintf(debugFP, "%d ", option);
2799 /* Whatever this is, we are already not doing
2800 it, because we never agree to do anything
2801 non-default, so according to the protocol
2802 rules, we don't reply. */
2807 if (appData.debugMode)
2808 fprintf(debugFP, "\n<IAC ");
2809 /* Doubled IAC; pass it through */
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2815 /* Drop all other telnet commands on the floor */
2818 if (oldi > next_out)
2819 SendToPlayer(&buf[next_out], oldi - next_out);
2825 /* OK, this at least will *usually* work */
2826 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2830 if (loggedOn && !intfSet) {
2831 if (ics_type == ICS_ICC) {
2832 snprintf(str, MSG_SIZ,
2833 "/set-quietly interface %s\n/set-quietly style 12\n",
2835 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2836 strcat(str, "/set-2 51 1\n/set seek 1\n");
2837 } else if (ics_type == ICS_CHESSNET) {
2838 snprintf(str, MSG_SIZ, "/style 12\n");
2840 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2841 strcat(str, programVersion);
2842 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2843 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2844 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2846 strcat(str, "$iset nohighlight 1\n");
2848 strcat(str, "$iset lock 1\n$style 12\n");
2851 NotifyFrontendLogin();
2855 if (started == STARTED_COMMENT) {
2856 /* Accumulate characters in comment */
2857 parse[parse_pos++] = buf[i];
2858 if (buf[i] == '\n') {
2859 parse[parse_pos] = NULLCHAR;
2860 if(chattingPartner>=0) {
2862 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2863 OutputChatMessage(chattingPartner, mess);
2864 chattingPartner = -1;
2865 next_out = i+1; // [HGM] suppress printing in ICS window
2867 if(!suppressKibitz) // [HGM] kibitz
2868 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2869 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2870 int nrDigit = 0, nrAlph = 0, j;
2871 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2872 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2873 parse[parse_pos] = NULLCHAR;
2874 // try to be smart: if it does not look like search info, it should go to
2875 // ICS interaction window after all, not to engine-output window.
2876 for(j=0; j<parse_pos; j++) { // count letters and digits
2877 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2878 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2879 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2881 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2882 int depth=0; float score;
2883 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2884 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2885 pvInfoList[forwardMostMove-1].depth = depth;
2886 pvInfoList[forwardMostMove-1].score = 100*score;
2888 OutputKibitz(suppressKibitz, parse);
2891 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2892 SendToPlayer(tmp, strlen(tmp));
2894 next_out = i+1; // [HGM] suppress printing in ICS window
2896 started = STARTED_NONE;
2898 /* Don't match patterns against characters in comment */
2903 if (started == STARTED_CHATTER) {
2904 if (buf[i] != '\n') {
2905 /* Don't match patterns against characters in chatter */
2909 started = STARTED_NONE;
2910 if(suppressKibitz) next_out = i+1;
2913 /* Kludge to deal with rcmd protocol */
2914 if (firstTime && looking_at(buf, &i, "\001*")) {
2915 DisplayFatalError(&buf[1], 0, 1);
2921 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2924 if (appData.debugMode)
2925 fprintf(debugFP, "ics_type %d\n", ics_type);
2928 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2929 ics_type = ICS_FICS;
2931 if (appData.debugMode)
2932 fprintf(debugFP, "ics_type %d\n", ics_type);
2935 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2936 ics_type = ICS_CHESSNET;
2938 if (appData.debugMode)
2939 fprintf(debugFP, "ics_type %d\n", ics_type);
2944 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2945 looking_at(buf, &i, "Logging you in as \"*\"") ||
2946 looking_at(buf, &i, "will be \"*\""))) {
2947 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2951 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2953 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2954 DisplayIcsInteractionTitle(buf);
2955 have_set_title = TRUE;
2958 /* skip finger notes */
2959 if (started == STARTED_NONE &&
2960 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2961 (buf[i] == '1' && buf[i+1] == '0')) &&
2962 buf[i+2] == ':' && buf[i+3] == ' ') {
2963 started = STARTED_CHATTER;
2969 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2970 if(appData.seekGraph) {
2971 if(soughtPending && MatchSoughtLine(buf+i)) {
2972 i = strstr(buf+i, "rated") - buf;
2973 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2974 next_out = leftover_start = i;
2975 started = STARTED_CHATTER;
2976 suppressKibitz = TRUE;
2979 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2980 && looking_at(buf, &i, "* ads displayed")) {
2981 soughtPending = FALSE;
2986 if(appData.autoRefresh) {
2987 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2988 int s = (ics_type == ICS_ICC); // ICC format differs
2990 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2991 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2992 looking_at(buf, &i, "*% "); // eat prompt
2993 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2994 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2995 next_out = i; // suppress
2998 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2999 char *p = star_match[0];
3001 if(seekGraphUp) RemoveSeekAd(atoi(p));
3002 while(*p && *p++ != ' '); // next
3004 looking_at(buf, &i, "*% "); // eat prompt
3005 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3012 /* skip formula vars */
3013 if (started == STARTED_NONE &&
3014 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3015 started = STARTED_CHATTER;
3020 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3021 if (appData.autoKibitz && started == STARTED_NONE &&
3022 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3023 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3024 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3025 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3026 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3027 suppressKibitz = TRUE;
3028 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3031 && (gameMode == IcsPlayingWhite)) ||
3032 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3033 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3034 started = STARTED_CHATTER; // own kibitz we simply discard
3036 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3037 parse_pos = 0; parse[0] = NULLCHAR;
3038 savingComment = TRUE;
3039 suppressKibitz = gameMode != IcsObserving ? 2 :
3040 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3044 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3045 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3046 && atoi(star_match[0])) {
3047 // suppress the acknowledgements of our own autoKibitz
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3051 SendToPlayer(star_match[0], strlen(star_match[0]));
3052 if(looking_at(buf, &i, "*% ")) // eat prompt
3053 suppressKibitz = FALSE;
3057 } // [HGM] kibitz: end of patch
3059 // [HGM] chat: intercept tells by users for which we have an open chat window
3061 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3062 looking_at(buf, &i, "* whispers:") ||
3063 looking_at(buf, &i, "* kibitzes:") ||
3064 looking_at(buf, &i, "* shouts:") ||
3065 looking_at(buf, &i, "* c-shouts:") ||
3066 looking_at(buf, &i, "--> * ") ||
3067 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3068 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3069 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3070 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3072 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3073 chattingPartner = -1;
3075 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3076 for(p=0; p<MAX_CHAT; p++) {
3077 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3078 talker[0] = '['; strcat(talker, "] ");
3079 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3080 chattingPartner = p; break;
3083 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3084 for(p=0; p<MAX_CHAT; p++) {
3085 if(!strcmp("kibitzes", chatPartner[p])) {
3086 talker[0] = '['; strcat(talker, "] ");
3087 chattingPartner = p; break;
3090 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3091 for(p=0; p<MAX_CHAT; p++) {
3092 if(!strcmp("whispers", chatPartner[p])) {
3093 talker[0] = '['; strcat(talker, "] ");
3094 chattingPartner = p; break;
3097 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3098 if(buf[i-8] == '-' && buf[i-3] == 't')
3099 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3100 if(!strcmp("c-shouts", chatPartner[p])) {
3101 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3102 chattingPartner = p; break;
3105 if(chattingPartner < 0)
3106 for(p=0; p<MAX_CHAT; p++) {
3107 if(!strcmp("shouts", chatPartner[p])) {
3108 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3109 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3110 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3111 chattingPartner = p; break;
3115 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3116 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3117 talker[0] = 0; Colorize(ColorTell, FALSE);
3118 chattingPartner = p; break;
3120 if(chattingPartner<0) i = oldi; else {
3121 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3122 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3123 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124 started = STARTED_COMMENT;
3125 parse_pos = 0; parse[0] = NULLCHAR;
3126 savingComment = 3 + chattingPartner; // counts as TRUE
3127 suppressKibitz = TRUE;
3130 } // [HGM] chat: end of patch
3133 if (appData.zippyTalk || appData.zippyPlay) {
3134 /* [DM] Backup address for color zippy lines */
3136 if (loggedOn == TRUE)
3137 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3138 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3140 } // [DM] 'else { ' deleted
3142 /* Regular tells and says */
3143 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3144 looking_at(buf, &i, "* (your partner) tells you: ") ||
3145 looking_at(buf, &i, "* says: ") ||
3146 /* Don't color "message" or "messages" output */
3147 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3148 looking_at(buf, &i, "*. * at *:*: ") ||
3149 looking_at(buf, &i, "--* (*:*): ") ||
3150 /* Message notifications (same color as tells) */
3151 looking_at(buf, &i, "* has left a message ") ||
3152 looking_at(buf, &i, "* just sent you a message:\n") ||
3153 /* Whispers and kibitzes */
3154 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3155 looking_at(buf, &i, "* kibitzes: ") ||
3157 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3159 if (tkind == 1 && strchr(star_match[0], ':')) {
3160 /* Avoid "tells you:" spoofs in channels */
3163 if (star_match[0][0] == NULLCHAR ||
3164 strchr(star_match[0], ' ') ||
3165 (tkind == 3 && strchr(star_match[1], ' '))) {
3166 /* Reject bogus matches */
3169 if (appData.colorize) {
3170 if (oldi > next_out) {
3171 SendToPlayer(&buf[next_out], oldi - next_out);
3176 Colorize(ColorTell, FALSE);
3177 curColor = ColorTell;
3180 Colorize(ColorKibitz, FALSE);
3181 curColor = ColorKibitz;
3184 p = strrchr(star_match[1], '(');
3191 Colorize(ColorChannel1, FALSE);
3192 curColor = ColorChannel1;
3194 Colorize(ColorChannel, FALSE);
3195 curColor = ColorChannel;
3199 curColor = ColorNormal;
3203 if (started == STARTED_NONE && appData.autoComment &&
3204 (gameMode == IcsObserving ||
3205 gameMode == IcsPlayingWhite ||
3206 gameMode == IcsPlayingBlack)) {
3207 parse_pos = i - oldi;
3208 memcpy(parse, &buf[oldi], parse_pos);
3209 parse[parse_pos] = NULLCHAR;
3210 started = STARTED_COMMENT;
3211 savingComment = TRUE;
3213 started = STARTED_CHATTER;
3214 savingComment = FALSE;
3221 if (looking_at(buf, &i, "* s-shouts: ") ||
3222 looking_at(buf, &i, "* c-shouts: ")) {
3223 if (appData.colorize) {
3224 if (oldi > next_out) {
3225 SendToPlayer(&buf[next_out], oldi - next_out);
3228 Colorize(ColorSShout, FALSE);
3229 curColor = ColorSShout;
3232 started = STARTED_CHATTER;
3236 if (looking_at(buf, &i, "--->")) {
3241 if (looking_at(buf, &i, "* shouts: ") ||
3242 looking_at(buf, &i, "--> ")) {
3243 if (appData.colorize) {
3244 if (oldi > next_out) {
3245 SendToPlayer(&buf[next_out], oldi - next_out);
3248 Colorize(ColorShout, FALSE);
3249 curColor = ColorShout;
3252 started = STARTED_CHATTER;
3256 if (looking_at( buf, &i, "Challenge:")) {
3257 if (appData.colorize) {
3258 if (oldi > next_out) {
3259 SendToPlayer(&buf[next_out], oldi - next_out);
3262 Colorize(ColorChallenge, FALSE);
3263 curColor = ColorChallenge;
3269 if (looking_at(buf, &i, "* offers you") ||
3270 looking_at(buf, &i, "* offers to be") ||
3271 looking_at(buf, &i, "* would like to") ||
3272 looking_at(buf, &i, "* requests to") ||
3273 looking_at(buf, &i, "Your opponent offers") ||
3274 looking_at(buf, &i, "Your opponent requests")) {
3276 if (appData.colorize) {
3277 if (oldi > next_out) {
3278 SendToPlayer(&buf[next_out], oldi - next_out);
3281 Colorize(ColorRequest, FALSE);
3282 curColor = ColorRequest;
3287 if (looking_at(buf, &i, "* (*) seeking")) {
3288 if (appData.colorize) {
3289 if (oldi > next_out) {
3290 SendToPlayer(&buf[next_out], oldi - next_out);
3293 Colorize(ColorSeek, FALSE);
3294 curColor = ColorSeek;
3299 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3301 if (looking_at(buf, &i, "\\ ")) {
3302 if (prevColor != ColorNormal) {
3303 if (oldi > next_out) {
3304 SendToPlayer(&buf[next_out], oldi - next_out);
3307 Colorize(prevColor, TRUE);
3308 curColor = prevColor;
3310 if (savingComment) {
3311 parse_pos = i - oldi;
3312 memcpy(parse, &buf[oldi], parse_pos);
3313 parse[parse_pos] = NULLCHAR;
3314 started = STARTED_COMMENT;
3315 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3316 chattingPartner = savingComment - 3; // kludge to remember the box
3318 started = STARTED_CHATTER;
3323 if (looking_at(buf, &i, "Black Strength :") ||
3324 looking_at(buf, &i, "<<< style 10 board >>>") ||
3325 looking_at(buf, &i, "<10>") ||
3326 looking_at(buf, &i, "#@#")) {
3327 /* Wrong board style */
3329 SendToICS(ics_prefix);
3330 SendToICS("set style 12\n");
3331 SendToICS(ics_prefix);
3332 SendToICS("refresh\n");
3336 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3338 have_sent_ICS_logon = 1;
3342 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3343 (looking_at(buf, &i, "\n<12> ") ||
3344 looking_at(buf, &i, "<12> "))) {
3346 if (oldi > next_out) {
3347 SendToPlayer(&buf[next_out], oldi - next_out);
3350 started = STARTED_BOARD;
3355 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3356 looking_at(buf, &i, "<b1> ")) {
3357 if (oldi > next_out) {
3358 SendToPlayer(&buf[next_out], oldi - next_out);
3361 started = STARTED_HOLDINGS;
3366 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3368 /* Header for a move list -- first line */
3370 switch (ics_getting_history) {
3374 case BeginningOfGame:
3375 /* User typed "moves" or "oldmoves" while we
3376 were idle. Pretend we asked for these
3377 moves and soak them up so user can step
3378 through them and/or save them.
3381 gameMode = IcsObserving;
3384 ics_getting_history = H_GOT_UNREQ_HEADER;
3386 case EditGame: /*?*/
3387 case EditPosition: /*?*/
3388 /* Should above feature work in these modes too? */
3389 /* For now it doesn't */
3390 ics_getting_history = H_GOT_UNWANTED_HEADER;
3393 ics_getting_history = H_GOT_UNWANTED_HEADER;
3398 /* Is this the right one? */
3399 if (gameInfo.white && gameInfo.black &&
3400 strcmp(gameInfo.white, star_match[0]) == 0 &&
3401 strcmp(gameInfo.black, star_match[2]) == 0) {
3403 ics_getting_history = H_GOT_REQ_HEADER;
3406 case H_GOT_REQ_HEADER:
3407 case H_GOT_UNREQ_HEADER:
3408 case H_GOT_UNWANTED_HEADER:
3409 case H_GETTING_MOVES:
3410 /* Should not happen */
3411 DisplayError(_("Error gathering move list: two headers"), 0);
3412 ics_getting_history = H_FALSE;
3416 /* Save player ratings into gameInfo if needed */
3417 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3418 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3419 (gameInfo.whiteRating == -1 ||
3420 gameInfo.blackRating == -1)) {
3422 gameInfo.whiteRating = string_to_rating(star_match[1]);
3423 gameInfo.blackRating = string_to_rating(star_match[3]);
3424 if (appData.debugMode)
3425 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3426 gameInfo.whiteRating, gameInfo.blackRating);
3431 if (looking_at(buf, &i,
3432 "* * match, initial time: * minute*, increment: * second")) {
3433 /* Header for a move list -- second line */
3434 /* Initial board will follow if this is a wild game */
3435 if (gameInfo.event != NULL) free(gameInfo.event);
3436 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3437 gameInfo.event = StrSave(str);
3438 /* [HGM] we switched variant. Translate boards if needed. */
3439 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3443 if (looking_at(buf, &i, "Move ")) {
3444 /* Beginning of a move list */
3445 switch (ics_getting_history) {
3447 /* Normally should not happen */
3448 /* Maybe user hit reset while we were parsing */
3451 /* Happens if we are ignoring a move list that is not
3452 * the one we just requested. Common if the user
3453 * tries to observe two games without turning off
3456 case H_GETTING_MOVES:
3457 /* Should not happen */
3458 DisplayError(_("Error gathering move list: nested"), 0);
3459 ics_getting_history = H_FALSE;
3461 case H_GOT_REQ_HEADER:
3462 ics_getting_history = H_GETTING_MOVES;
3463 started = STARTED_MOVES;
3465 if (oldi > next_out) {
3466 SendToPlayer(&buf[next_out], oldi - next_out);
3469 case H_GOT_UNREQ_HEADER:
3470 ics_getting_history = H_GETTING_MOVES;
3471 started = STARTED_MOVES_NOHIDE;
3474 case H_GOT_UNWANTED_HEADER:
3475 ics_getting_history = H_FALSE;
3481 if (looking_at(buf, &i, "% ") ||
3482 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3483 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3484 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3485 soughtPending = FALSE;
3489 if(suppressKibitz) next_out = i;
3490 savingComment = FALSE;
3494 case STARTED_MOVES_NOHIDE:
3495 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3496 parse[parse_pos + i - oldi] = NULLCHAR;
3497 ParseGameHistory(parse);
3499 if (appData.zippyPlay && first.initDone) {
3500 FeedMovesToProgram(&first, forwardMostMove);
3501 if (gameMode == IcsPlayingWhite) {
3502 if (WhiteOnMove(forwardMostMove)) {
3503 if (first.sendTime) {
3504 if (first.useColors) {
3505 SendToProgram("black\n", &first);
3507 SendTimeRemaining(&first, TRUE);
3509 if (first.useColors) {
3510 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3512 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3513 first.maybeThinking = TRUE;
3515 if (first.usePlayother) {
3516 if (first.sendTime) {
3517 SendTimeRemaining(&first, TRUE);
3519 SendToProgram("playother\n", &first);
3525 } else if (gameMode == IcsPlayingBlack) {
3526 if (!WhiteOnMove(forwardMostMove)) {
3527 if (first.sendTime) {
3528 if (first.useColors) {
3529 SendToProgram("white\n", &first);
3531 SendTimeRemaining(&first, FALSE);
3533 if (first.useColors) {
3534 SendToProgram("black\n", &first);
3536 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3537 first.maybeThinking = TRUE;
3539 if (first.usePlayother) {
3540 if (first.sendTime) {
3541 SendTimeRemaining(&first, FALSE);
3543 SendToProgram("playother\n", &first);
3552 if (gameMode == IcsObserving && ics_gamenum == -1) {
3553 /* Moves came from oldmoves or moves command
3554 while we weren't doing anything else.
3556 currentMove = forwardMostMove;
3557 ClearHighlights();/*!!could figure this out*/
3558 flipView = appData.flipView;
3559 DrawPosition(TRUE, boards[currentMove]);
3560 DisplayBothClocks();
3561 snprintf(str, MSG_SIZ, "%s vs. %s",
3562 gameInfo.white, gameInfo.black);
3566 /* Moves were history of an active game */
3567 if (gameInfo.resultDetails != NULL) {
3568 free(gameInfo.resultDetails);
3569 gameInfo.resultDetails = NULL;
3572 HistorySet(parseList, backwardMostMove,
3573 forwardMostMove, currentMove-1);
3574 DisplayMove(currentMove - 1);
3575 if (started == STARTED_MOVES) next_out = i;
3576 started = STARTED_NONE;
3577 ics_getting_history = H_FALSE;
3580 case STARTED_OBSERVE:
3581 started = STARTED_NONE;
3582 SendToICS(ics_prefix);
3583 SendToICS("refresh\n");
3589 if(bookHit) { // [HGM] book: simulate book reply
3590 static char bookMove[MSG_SIZ]; // a bit generous?
3592 programStats.nodes = programStats.depth = programStats.time =
3593 programStats.score = programStats.got_only_move = 0;
3594 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3596 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3597 strcat(bookMove, bookHit);
3598 HandleMachineMove(bookMove, &first);
3603 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3604 started == STARTED_HOLDINGS ||
3605 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3606 /* Accumulate characters in move list or board */
3607 parse[parse_pos++] = buf[i];
3610 /* Start of game messages. Mostly we detect start of game
3611 when the first board image arrives. On some versions
3612 of the ICS, though, we need to do a "refresh" after starting
3613 to observe in order to get the current board right away. */
3614 if (looking_at(buf, &i, "Adding game * to observation list")) {
3615 started = STARTED_OBSERVE;
3619 /* Handle auto-observe */
3620 if (appData.autoObserve &&
3621 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3622 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3624 /* Choose the player that was highlighted, if any. */
3625 if (star_match[0][0] == '\033' ||
3626 star_match[1][0] != '\033') {
3627 player = star_match[0];
3629 player = star_match[2];
3631 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3632 ics_prefix, StripHighlightAndTitle(player));
3635 /* Save ratings from notify string */
3636 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3637 player1Rating = string_to_rating(star_match[1]);
3638 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3639 player2Rating = string_to_rating(star_match[3]);
3641 if (appData.debugMode)
3643 "Ratings from 'Game notification:' %s %d, %s %d\n",
3644 player1Name, player1Rating,
3645 player2Name, player2Rating);
3650 /* Deal with automatic examine mode after a game,
3651 and with IcsObserving -> IcsExamining transition */
3652 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3653 looking_at(buf, &i, "has made you an examiner of game *")) {
3655 int gamenum = atoi(star_match[0]);
3656 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3657 gamenum == ics_gamenum) {
3658 /* We were already playing or observing this game;
3659 no need to refetch history */
3660 gameMode = IcsExamining;
3662 pauseExamForwardMostMove = forwardMostMove;
3663 } else if (currentMove < forwardMostMove) {
3664 ForwardInner(forwardMostMove);
3667 /* I don't think this case really can happen */
3668 SendToICS(ics_prefix);
3669 SendToICS("refresh\n");
3674 /* Error messages */
3675 // if (ics_user_moved) {
3676 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3677 if (looking_at(buf, &i, "Illegal move") ||
3678 looking_at(buf, &i, "Not a legal move") ||
3679 looking_at(buf, &i, "Your king is in check") ||
3680 looking_at(buf, &i, "It isn't your turn") ||
3681 looking_at(buf, &i, "It is not your move")) {
3683 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3684 currentMove = forwardMostMove-1;
3685 DisplayMove(currentMove - 1); /* before DMError */
3686 DrawPosition(FALSE, boards[currentMove]);
3687 SwitchClocks(forwardMostMove-1); // [HGM] race
3688 DisplayBothClocks();
3690 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3696 if (looking_at(buf, &i, "still have time") ||
3697 looking_at(buf, &i, "not out of time") ||
3698 looking_at(buf, &i, "either player is out of time") ||
3699 looking_at(buf, &i, "has timeseal; checking")) {
3700 /* We must have called his flag a little too soon */
3701 whiteFlag = blackFlag = FALSE;
3705 if (looking_at(buf, &i, "added * seconds to") ||
3706 looking_at(buf, &i, "seconds were added to")) {
3707 /* Update the clocks */
3708 SendToICS(ics_prefix);
3709 SendToICS("refresh\n");
3713 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3714 ics_clock_paused = TRUE;
3719 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3720 ics_clock_paused = FALSE;
3725 /* Grab player ratings from the Creating: message.
3726 Note we have to check for the special case when
3727 the ICS inserts things like [white] or [black]. */
3728 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3729 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3731 0 player 1 name (not necessarily white)
3733 2 empty, white, or black (IGNORED)
3734 3 player 2 name (not necessarily black)
3737 The names/ratings are sorted out when the game
3738 actually starts (below).
3740 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3741 player1Rating = string_to_rating(star_match[1]);
3742 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3743 player2Rating = string_to_rating(star_match[4]);
3745 if (appData.debugMode)
3747 "Ratings from 'Creating:' %s %d, %s %d\n",
3748 player1Name, player1Rating,
3749 player2Name, player2Rating);
3754 /* Improved generic start/end-of-game messages */
3755 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3756 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3757 /* If tkind == 0: */
3758 /* star_match[0] is the game number */
3759 /* [1] is the white player's name */
3760 /* [2] is the black player's name */
3761 /* For end-of-game: */
3762 /* [3] is the reason for the game end */
3763 /* [4] is a PGN end game-token, preceded by " " */
3764 /* For start-of-game: */
3765 /* [3] begins with "Creating" or "Continuing" */
3766 /* [4] is " *" or empty (don't care). */
3767 int gamenum = atoi(star_match[0]);
3768 char *whitename, *blackname, *why, *endtoken;
3769 ChessMove endtype = EndOfFile;
3772 whitename = star_match[1];
3773 blackname = star_match[2];
3774 why = star_match[3];
3775 endtoken = star_match[4];
3777 whitename = star_match[1];
3778 blackname = star_match[3];
3779 why = star_match[5];
3780 endtoken = star_match[6];
3783 /* Game start messages */
3784 if (strncmp(why, "Creating ", 9) == 0 ||
3785 strncmp(why, "Continuing ", 11) == 0) {
3786 gs_gamenum = gamenum;
3787 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3788 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3790 if (appData.zippyPlay) {
3791 ZippyGameStart(whitename, blackname);
3794 partnerBoardValid = FALSE; // [HGM] bughouse
3798 /* Game end messages */
3799 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3800 ics_gamenum != gamenum) {
3803 while (endtoken[0] == ' ') endtoken++;
3804 switch (endtoken[0]) {
3807 endtype = GameUnfinished;
3810 endtype = BlackWins;
3813 if (endtoken[1] == '/')
3814 endtype = GameIsDrawn;
3816 endtype = WhiteWins;
3819 GameEnds(endtype, why, GE_ICS);
3821 if (appData.zippyPlay && first.initDone) {
3822 ZippyGameEnd(endtype, why);
3823 if (first.pr == NULL) {
3824 /* Start the next process early so that we'll
3825 be ready for the next challenge */
3826 StartChessProgram(&first);
3828 /* Send "new" early, in case this command takes
3829 a long time to finish, so that we'll be ready
3830 for the next challenge. */
3831 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3835 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3839 if (looking_at(buf, &i, "Removing game * from observation") ||
3840 looking_at(buf, &i, "no longer observing game *") ||
3841 looking_at(buf, &i, "Game * (*) has no examiners")) {
3842 if (gameMode == IcsObserving &&
3843 atoi(star_match[0]) == ics_gamenum)
3845 /* icsEngineAnalyze */
3846 if (appData.icsEngineAnalyze) {
3853 ics_user_moved = FALSE;
3858 if (looking_at(buf, &i, "no longer examining game *")) {
3859 if (gameMode == IcsExamining &&
3860 atoi(star_match[0]) == ics_gamenum)
3864 ics_user_moved = FALSE;
3869 /* Advance leftover_start past any newlines we find,
3870 so only partial lines can get reparsed */
3871 if (looking_at(buf, &i, "\n")) {
3872 prevColor = curColor;
3873 if (curColor != ColorNormal) {
3874 if (oldi > next_out) {
3875 SendToPlayer(&buf[next_out], oldi - next_out);
3878 Colorize(ColorNormal, FALSE);
3879 curColor = ColorNormal;
3881 if (started == STARTED_BOARD) {
3882 started = STARTED_NONE;
3883 parse[parse_pos] = NULLCHAR;
3884 ParseBoard12(parse);
3887 /* Send premove here */
3888 if (appData.premove) {
3890 if (currentMove == 0 &&
3891 gameMode == IcsPlayingWhite &&
3892 appData.premoveWhite) {
3893 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3894 if (appData.debugMode)
3895 fprintf(debugFP, "Sending premove:\n");
3897 } else if (currentMove == 1 &&
3898 gameMode == IcsPlayingBlack &&
3899 appData.premoveBlack) {
3900 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3901 if (appData.debugMode)
3902 fprintf(debugFP, "Sending premove:\n");
3904 } else if (gotPremove) {
3906 ClearPremoveHighlights();
3907 if (appData.debugMode)
3908 fprintf(debugFP, "Sending premove:\n");
3909 UserMoveEvent(premoveFromX, premoveFromY,
3910 premoveToX, premoveToY,
3915 /* Usually suppress following prompt */
3916 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3917 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3918 if (looking_at(buf, &i, "*% ")) {
3919 savingComment = FALSE;
3924 } else if (started == STARTED_HOLDINGS) {
3926 char new_piece[MSG_SIZ];
3927 started = STARTED_NONE;
3928 parse[parse_pos] = NULLCHAR;
3929 if (appData.debugMode)
3930 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3931 parse, currentMove);
3932 if (sscanf(parse, " game %d", &gamenum) == 1) {
3933 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3934 if (gameInfo.variant == VariantNormal) {
3935 /* [HGM] We seem to switch variant during a game!
3936 * Presumably no holdings were displayed, so we have
3937 * to move the position two files to the right to
3938 * create room for them!
3940 VariantClass newVariant;
3941 switch(gameInfo.boardWidth) { // base guess on board width
3942 case 9: newVariant = VariantShogi; break;
3943 case 10: newVariant = VariantGreat; break;
3944 default: newVariant = VariantCrazyhouse; break;
3946 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3947 /* Get a move list just to see the header, which
3948 will tell us whether this is really bug or zh */
3949 if (ics_getting_history == H_FALSE) {
3950 ics_getting_history = H_REQUESTED;
3951 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3955 new_piece[0] = NULLCHAR;
3956 sscanf(parse, "game %d white [%s black [%s <- %s",
3957 &gamenum, white_holding, black_holding,
3959 white_holding[strlen(white_holding)-1] = NULLCHAR;
3960 black_holding[strlen(black_holding)-1] = NULLCHAR;
3961 /* [HGM] copy holdings to board holdings area */
3962 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3963 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3964 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3966 if (appData.zippyPlay && first.initDone) {
3967 ZippyHoldings(white_holding, black_holding,
3971 if (tinyLayout || smallLayout) {
3972 char wh[16], bh[16];
3973 PackHolding(wh, white_holding);
3974 PackHolding(bh, black_holding);
3975 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3976 gameInfo.white, gameInfo.black);
3978 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3979 gameInfo.white, white_holding,
3980 gameInfo.black, black_holding);
3982 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3983 DrawPosition(FALSE, boards[currentMove]);
3985 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3986 sscanf(parse, "game %d white [%s black [%s <- %s",
3987 &gamenum, white_holding, black_holding,
3989 white_holding[strlen(white_holding)-1] = NULLCHAR;
3990 black_holding[strlen(black_holding)-1] = NULLCHAR;
3991 /* [HGM] copy holdings to partner-board holdings area */
3992 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3993 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3994 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3995 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3996 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3999 /* Suppress following prompt */
4000 if (looking_at(buf, &i, "*% ")) {
4001 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4002 savingComment = FALSE;
4010 i++; /* skip unparsed character and loop back */
4013 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4014 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4015 // SendToPlayer(&buf[next_out], i - next_out);
4016 started != STARTED_HOLDINGS && leftover_start > next_out) {
4017 SendToPlayer(&buf[next_out], leftover_start - next_out);
4021 leftover_len = buf_len - leftover_start;
4022 /* if buffer ends with something we couldn't parse,
4023 reparse it after appending the next read */
4025 } else if (count == 0) {
4026 RemoveInputSource(isr);
4027 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4029 DisplayFatalError(_("Error reading from ICS"), error, 1);
4034 /* Board style 12 looks like this:
4036 <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
4038 * The "<12> " is stripped before it gets to this routine. The two
4039 * trailing 0's (flip state and clock ticking) are later addition, and
4040 * some chess servers may not have them, or may have only the first.
4041 * Additional trailing fields may be added in the future.
4044 #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"
4046 #define RELATION_OBSERVING_PLAYED 0
4047 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4048 #define RELATION_PLAYING_MYMOVE 1
4049 #define RELATION_PLAYING_NOTMYMOVE -1
4050 #define RELATION_EXAMINING 2
4051 #define RELATION_ISOLATED_BOARD -3
4052 #define RELATION_STARTING_POSITION -4 /* FICS only */
4055 ParseBoard12(string)
4058 GameMode newGameMode;
4059 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4060 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4061 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4062 char to_play, board_chars[200];
4063 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4064 char black[32], white[32];
4066 int prevMove = currentMove;
4069 int fromX, fromY, toX, toY;
4071 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4072 char *bookHit = NULL; // [HGM] book
4073 Boolean weird = FALSE, reqFlag = FALSE;
4075 fromX = fromY = toX = toY = -1;
4079 if (appData.debugMode)
4080 fprintf(debugFP, _("Parsing board: %s\n"), string);
4082 move_str[0] = NULLCHAR;
4083 elapsed_time[0] = NULLCHAR;
4084 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4086 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4087 if(string[i] == ' ') { ranks++; files = 0; }
4089 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4092 for(j = 0; j <i; j++) board_chars[j] = string[j];
4093 board_chars[i] = '\0';
4096 n = sscanf(string, PATTERN, &to_play, &double_push,
4097 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4098 &gamenum, white, black, &relation, &basetime, &increment,
4099 &white_stren, &black_stren, &white_time, &black_time,
4100 &moveNum, str, elapsed_time, move_str, &ics_flip,
4104 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4105 DisplayError(str, 0);
4109 /* Convert the move number to internal form */
4110 moveNum = (moveNum - 1) * 2;
4111 if (to_play == 'B') moveNum++;
4112 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4113 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4119 case RELATION_OBSERVING_PLAYED:
4120 case RELATION_OBSERVING_STATIC:
4121 if (gamenum == -1) {
4122 /* Old ICC buglet */
4123 relation = RELATION_OBSERVING_STATIC;
4125 newGameMode = IcsObserving;
4127 case RELATION_PLAYING_MYMOVE:
4128 case RELATION_PLAYING_NOTMYMOVE:
4130 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4131 IcsPlayingWhite : IcsPlayingBlack;
4133 case RELATION_EXAMINING:
4134 newGameMode = IcsExamining;
4136 case RELATION_ISOLATED_BOARD:
4138 /* Just display this board. If user was doing something else,
4139 we will forget about it until the next board comes. */
4140 newGameMode = IcsIdle;
4142 case RELATION_STARTING_POSITION:
4143 newGameMode = gameMode;
4147 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4148 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4149 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4151 for (k = 0; k < ranks; k++) {
4152 for (j = 0; j < files; j++)
4153 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4154 if(gameInfo.holdingsWidth > 1) {
4155 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4156 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4159 CopyBoard(partnerBoard, board);
4160 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4161 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4162 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4163 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4164 if(toSqr = strchr(str, '-')) {
4165 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4166 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4167 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4168 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4169 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4170 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4171 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4172 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4173 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4174 DisplayMessage(partnerStatus, "");
4175 partnerBoardValid = TRUE;
4179 /* Modify behavior for initial board display on move listing
4182 switch (ics_getting_history) {
4186 case H_GOT_REQ_HEADER:
4187 case H_GOT_UNREQ_HEADER:
4188 /* This is the initial position of the current game */
4189 gamenum = ics_gamenum;
4190 moveNum = 0; /* old ICS bug workaround */
4191 if (to_play == 'B') {
4192 startedFromSetupPosition = TRUE;
4193 blackPlaysFirst = TRUE;
4195 if (forwardMostMove == 0) forwardMostMove = 1;
4196 if (backwardMostMove == 0) backwardMostMove = 1;
4197 if (currentMove == 0) currentMove = 1;
4199 newGameMode = gameMode;
4200 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4202 case H_GOT_UNWANTED_HEADER:
4203 /* This is an initial board that we don't want */
4205 case H_GETTING_MOVES:
4206 /* Should not happen */
4207 DisplayError(_("Error gathering move list: extra board"), 0);
4208 ics_getting_history = H_FALSE;
4212 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4213 weird && (int)gameInfo.variant < (int)VariantShogi) {
4214 /* [HGM] We seem to have switched variant unexpectedly
4215 * Try to guess new variant from board size
4217 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4218 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4219 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4220 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4221 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4222 if(!weird) newVariant = VariantNormal;
4223 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224 /* Get a move list just to see the header, which
4225 will tell us whether this is really bug or zh */
4226 if (ics_getting_history == H_FALSE) {
4227 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4228 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4233 /* Take action if this is the first board of a new game, or of a
4234 different game than is currently being displayed. */
4235 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4236 relation == RELATION_ISOLATED_BOARD) {
4238 /* Forget the old game and get the history (if any) of the new one */
4239 if (gameMode != BeginningOfGame) {
4243 if (appData.autoRaiseBoard) BoardToTop();
4245 if (gamenum == -1) {
4246 newGameMode = IcsIdle;
4247 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4248 appData.getMoveList && !reqFlag) {
4249 /* Need to get game history */
4250 ics_getting_history = H_REQUESTED;
4251 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4255 /* Initially flip the board to have black on the bottom if playing
4256 black or if the ICS flip flag is set, but let the user change
4257 it with the Flip View button. */
4258 flipView = appData.autoFlipView ?
4259 (newGameMode == IcsPlayingBlack) || ics_flip :
4262 /* Done with values from previous mode; copy in new ones */
4263 gameMode = newGameMode;
4265 ics_gamenum = gamenum;
4266 if (gamenum == gs_gamenum) {
4267 int klen = strlen(gs_kind);
4268 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4269 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4270 gameInfo.event = StrSave(str);
4272 gameInfo.event = StrSave("ICS game");
4274 gameInfo.site = StrSave(appData.icsHost);
4275 gameInfo.date = PGNDate();
4276 gameInfo.round = StrSave("-");
4277 gameInfo.white = StrSave(white);
4278 gameInfo.black = StrSave(black);
4279 timeControl = basetime * 60 * 1000;
4281 timeIncrement = increment * 1000;
4282 movesPerSession = 0;
4283 gameInfo.timeControl = TimeControlTagValue();
4284 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4285 if (appData.debugMode) {
4286 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4287 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4288 setbuf(debugFP, NULL);
4291 gameInfo.outOfBook = NULL;
4293 /* Do we have the ratings? */
4294 if (strcmp(player1Name, white) == 0 &&
4295 strcmp(player2Name, black) == 0) {
4296 if (appData.debugMode)
4297 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4298 player1Rating, player2Rating);
4299 gameInfo.whiteRating = player1Rating;
4300 gameInfo.blackRating = player2Rating;
4301 } else if (strcmp(player2Name, white) == 0 &&
4302 strcmp(player1Name, black) == 0) {
4303 if (appData.debugMode)
4304 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4305 player2Rating, player1Rating);
4306 gameInfo.whiteRating = player2Rating;
4307 gameInfo.blackRating = player1Rating;
4309 player1Name[0] = player2Name[0] = NULLCHAR;
4311 /* Silence shouts if requested */
4312 if (appData.quietPlay &&
4313 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4314 SendToICS(ics_prefix);
4315 SendToICS("set shout 0\n");
4319 /* Deal with midgame name changes */
4321 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4322 if (gameInfo.white) free(gameInfo.white);
4323 gameInfo.white = StrSave(white);
4325 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4326 if (gameInfo.black) free(gameInfo.black);
4327 gameInfo.black = StrSave(black);
4331 /* Throw away game result if anything actually changes in examine mode */
4332 if (gameMode == IcsExamining && !newGame) {
4333 gameInfo.result = GameUnfinished;
4334 if (gameInfo.resultDetails != NULL) {
4335 free(gameInfo.resultDetails);
4336 gameInfo.resultDetails = NULL;
4340 /* In pausing && IcsExamining mode, we ignore boards coming
4341 in if they are in a different variation than we are. */
4342 if (pauseExamInvalid) return;
4343 if (pausing && gameMode == IcsExamining) {
4344 if (moveNum <= pauseExamForwardMostMove) {
4345 pauseExamInvalid = TRUE;
4346 forwardMostMove = pauseExamForwardMostMove;
4351 if (appData.debugMode) {
4352 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4354 /* Parse the board */
4355 for (k = 0; k < ranks; k++) {
4356 for (j = 0; j < files; j++)
4357 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4358 if(gameInfo.holdingsWidth > 1) {
4359 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4360 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4363 CopyBoard(boards[moveNum], board);
4364 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4366 startedFromSetupPosition =
4367 !CompareBoards(board, initialPosition);
4368 if(startedFromSetupPosition)
4369 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4372 /* [HGM] Set castling rights. Take the outermost Rooks,
4373 to make it also work for FRC opening positions. Note that board12
4374 is really defective for later FRC positions, as it has no way to
4375 indicate which Rook can castle if they are on the same side of King.
4376 For the initial position we grant rights to the outermost Rooks,
4377 and remember thos rights, and we then copy them on positions
4378 later in an FRC game. This means WB might not recognize castlings with
4379 Rooks that have moved back to their original position as illegal,
4380 but in ICS mode that is not its job anyway.
4382 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4383 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4385 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4386 if(board[0][i] == WhiteRook) j = i;
4387 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4388 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4389 if(board[0][i] == WhiteRook) j = i;
4390 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4391 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4392 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4393 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4394 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4395 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4396 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4398 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4399 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4400 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4401 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4402 if(board[BOARD_HEIGHT-1][k] == bKing)
4403 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4404 if(gameInfo.variant == VariantTwoKings) {
4405 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4406 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4407 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4410 r = boards[moveNum][CASTLING][0] = initialRights[0];
4411 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4412 r = boards[moveNum][CASTLING][1] = initialRights[1];
4413 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4414 r = boards[moveNum][CASTLING][3] = initialRights[3];
4415 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4416 r = boards[moveNum][CASTLING][4] = initialRights[4];
4417 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4418 /* wildcastle kludge: always assume King has rights */
4419 r = boards[moveNum][CASTLING][2] = initialRights[2];
4420 r = boards[moveNum][CASTLING][5] = initialRights[5];
4422 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4423 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4426 if (ics_getting_history == H_GOT_REQ_HEADER ||
4427 ics_getting_history == H_GOT_UNREQ_HEADER) {
4428 /* This was an initial position from a move list, not
4429 the current position */
4433 /* Update currentMove and known move number limits */
4434 newMove = newGame || moveNum > forwardMostMove;
4437 forwardMostMove = backwardMostMove = currentMove = moveNum;
4438 if (gameMode == IcsExamining && moveNum == 0) {
4439 /* Workaround for ICS limitation: we are not told the wild
4440 type when starting to examine a game. But if we ask for
4441 the move list, the move list header will tell us */
4442 ics_getting_history = H_REQUESTED;
4443 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4446 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4447 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4449 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4450 /* [HGM] applied this also to an engine that is silently watching */
4451 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4452 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4453 gameInfo.variant == currentlyInitializedVariant) {
4454 takeback = forwardMostMove - moveNum;
4455 for (i = 0; i < takeback; i++) {
4456 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4457 SendToProgram("undo\n", &first);
4462 forwardMostMove = moveNum;
4463 if (!pausing || currentMove > forwardMostMove)
4464 currentMove = forwardMostMove;
4466 /* New part of history that is not contiguous with old part */
4467 if (pausing && gameMode == IcsExamining) {
4468 pauseExamInvalid = TRUE;
4469 forwardMostMove = pauseExamForwardMostMove;
4472 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4474 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4475 // [HGM] when we will receive the move list we now request, it will be
4476 // fed to the engine from the first move on. So if the engine is not
4477 // in the initial position now, bring it there.
4478 InitChessProgram(&first, 0);
4481 ics_getting_history = H_REQUESTED;
4482 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4485 forwardMostMove = backwardMostMove = currentMove = moveNum;
4488 /* Update the clocks */
4489 if (strchr(elapsed_time, '.')) {
4491 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4492 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4494 /* Time is in seconds */
4495 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4496 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4501 if (appData.zippyPlay && newGame &&
4502 gameMode != IcsObserving && gameMode != IcsIdle &&
4503 gameMode != IcsExamining)
4504 ZippyFirstBoard(moveNum, basetime, increment);
4507 /* Put the move on the move list, first converting
4508 to canonical algebraic form. */
4510 if (appData.debugMode) {
4511 if (appData.debugMode) { int f = forwardMostMove;
4512 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4513 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4514 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4516 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4517 fprintf(debugFP, "moveNum = %d\n", moveNum);
4518 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4519 setbuf(debugFP, NULL);
4521 if (moveNum <= backwardMostMove) {
4522 /* We don't know what the board looked like before
4524 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4525 strcat(parseList[moveNum - 1], " ");
4526 strcat(parseList[moveNum - 1], elapsed_time);
4527 moveList[moveNum - 1][0] = NULLCHAR;
4528 } else if (strcmp(move_str, "none") == 0) {
4529 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4530 /* Again, we don't know what the board looked like;
4531 this is really the start of the game. */
4532 parseList[moveNum - 1][0] = NULLCHAR;
4533 moveList[moveNum - 1][0] = NULLCHAR;
4534 backwardMostMove = moveNum;
4535 startedFromSetupPosition = TRUE;
4536 fromX = fromY = toX = toY = -1;
4538 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4539 // So we parse the long-algebraic move string in stead of the SAN move
4540 int valid; char buf[MSG_SIZ], *prom;
4542 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4543 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4544 // str looks something like "Q/a1-a2"; kill the slash
4546 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4547 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4548 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4549 strcat(buf, prom); // long move lacks promo specification!
4550 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4551 if(appData.debugMode)
4552 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4553 safeStrCpy(move_str, buf, MSG_SIZ);
4555 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4556 &fromX, &fromY, &toX, &toY, &promoChar)
4557 || ParseOneMove(buf, moveNum - 1, &moveType,
4558 &fromX, &fromY, &toX, &toY, &promoChar);
4559 // end of long SAN patch
4561 (void) CoordsToAlgebraic(boards[moveNum - 1],
4562 PosFlags(moveNum - 1),
4563 fromY, fromX, toY, toX, promoChar,
4564 parseList[moveNum-1]);
4565 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4571 if(gameInfo.variant != VariantShogi)
4572 strcat(parseList[moveNum - 1], "+");
4575 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4576 strcat(parseList[moveNum - 1], "#");
4579 strcat(parseList[moveNum - 1], " ");
4580 strcat(parseList[moveNum - 1], elapsed_time);
4581 /* currentMoveString is set as a side-effect of ParseOneMove */
4582 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4583 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4584 strcat(moveList[moveNum - 1], "\n");
4586 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4587 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4588 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4589 ChessSquare old, new = boards[moveNum][k][j];
4590 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4591 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4592 if(old == new) continue;
4593 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4594 else if(new == WhiteWazir || new == BlackWazir) {
4595 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4596 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4597 else boards[moveNum][k][j] = old; // preserve type of Gold
4598 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4599 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4602 /* Move from ICS was illegal!? Punt. */
4603 if (appData.debugMode) {
4604 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4605 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4607 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4608 strcat(parseList[moveNum - 1], " ");
4609 strcat(parseList[moveNum - 1], elapsed_time);
4610 moveList[moveNum - 1][0] = NULLCHAR;
4611 fromX = fromY = toX = toY = -1;
4614 if (appData.debugMode) {
4615 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4616 setbuf(debugFP, NULL);
4620 /* Send move to chess program (BEFORE animating it). */
4621 if (appData.zippyPlay && !newGame && newMove &&
4622 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4624 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4625 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4626 if (moveList[moveNum - 1][0] == NULLCHAR) {
4627 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4629 DisplayError(str, 0);
4631 if (first.sendTime) {
4632 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4634 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4635 if (firstMove && !bookHit) {
4637 if (first.useColors) {
4638 SendToProgram(gameMode == IcsPlayingWhite ?
4640 "black\ngo\n", &first);
4642 SendToProgram("go\n", &first);
4644 first.maybeThinking = TRUE;
4647 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4648 if (moveList[moveNum - 1][0] == NULLCHAR) {
4649 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4650 DisplayError(str, 0);
4652 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4653 SendMoveToProgram(moveNum - 1, &first);
4660 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4661 /* If move comes from a remote source, animate it. If it
4662 isn't remote, it will have already been animated. */
4663 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4664 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4666 if (!pausing && appData.highlightLastMove) {
4667 SetHighlights(fromX, fromY, toX, toY);
4671 /* Start the clocks */
4672 whiteFlag = blackFlag = FALSE;
4673 appData.clockMode = !(basetime == 0 && increment == 0);
4675 ics_clock_paused = TRUE;
4677 } else if (ticking == 1) {
4678 ics_clock_paused = FALSE;
4680 if (gameMode == IcsIdle ||
4681 relation == RELATION_OBSERVING_STATIC ||
4682 relation == RELATION_EXAMINING ||
4684 DisplayBothClocks();
4688 /* Display opponents and material strengths */
4689 if (gameInfo.variant != VariantBughouse &&
4690 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4691 if (tinyLayout || smallLayout) {
4692 if(gameInfo.variant == VariantNormal)
4693 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4694 gameInfo.white, white_stren, gameInfo.black, black_stren,
4695 basetime, increment);
4697 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4698 gameInfo.white, white_stren, gameInfo.black, black_stren,
4699 basetime, increment, (int) gameInfo.variant);
4701 if(gameInfo.variant == VariantNormal)
4702 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4703 gameInfo.white, white_stren, gameInfo.black, black_stren,
4704 basetime, increment);
4706 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4707 gameInfo.white, white_stren, gameInfo.black, black_stren,
4708 basetime, increment, VariantName(gameInfo.variant));
4711 if (appData.debugMode) {
4712 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4717 /* Display the board */
4718 if (!pausing && !appData.noGUI) {
4720 if (appData.premove)
4722 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4723 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4724 ClearPremoveHighlights();
4726 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4727 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4728 DrawPosition(j, boards[currentMove]);
4730 DisplayMove(moveNum - 1);
4731 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4732 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4733 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4734 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4738 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4740 if(bookHit) { // [HGM] book: simulate book reply
4741 static char bookMove[MSG_SIZ]; // a bit generous?
4743 programStats.nodes = programStats.depth = programStats.time =
4744 programStats.score = programStats.got_only_move = 0;
4745 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4747 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4748 strcat(bookMove, bookHit);
4749 HandleMachineMove(bookMove, &first);
4758 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4759 ics_getting_history = H_REQUESTED;
4760 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4766 AnalysisPeriodicEvent(force)
4769 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4770 && !force) || !appData.periodicUpdates)
4773 /* Send . command to Crafty to collect stats */
4774 SendToProgram(".\n", &first);
4776 /* Don't send another until we get a response (this makes
4777 us stop sending to old Crafty's which don't understand
4778 the "." command (sending illegal cmds resets node count & time,
4779 which looks bad)) */
4780 programStats.ok_to_send = 0;
4783 void ics_update_width(new_width)
4786 ics_printf("set width %d\n", new_width);
4790 SendMoveToProgram(moveNum, cps)
4792 ChessProgramState *cps;
4796 if (cps->useUsermove) {
4797 SendToProgram("usermove ", cps);
4801 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4802 int len = space - parseList[moveNum];
4803 memcpy(buf, parseList[moveNum], len);
4805 buf[len] = NULLCHAR;
4807 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4809 SendToProgram(buf, cps);
4811 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4812 AlphaRank(moveList[moveNum], 4);
4813 SendToProgram(moveList[moveNum], cps);
4814 AlphaRank(moveList[moveNum], 4); // and back
4816 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4817 * the engine. It would be nice to have a better way to identify castle
4819 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4820 && cps->useOOCastle) {
4821 int fromX = moveList[moveNum][0] - AAA;
4822 int fromY = moveList[moveNum][1] - ONE;
4823 int toX = moveList[moveNum][2] - AAA;
4824 int toY = moveList[moveNum][3] - ONE;
4825 if((boards[moveNum][fromY][fromX] == WhiteKing
4826 && boards[moveNum][toY][toX] == WhiteRook)
4827 || (boards[moveNum][fromY][fromX] == BlackKing
4828 && boards[moveNum][toY][toX] == BlackRook)) {
4829 if(toX > fromX) SendToProgram("O-O\n", cps);
4830 else SendToProgram("O-O-O\n", cps);
4832 else SendToProgram(moveList[moveNum], cps);
4834 else SendToProgram(moveList[moveNum], cps);
4835 /* End of additions by Tord */
4838 /* [HGM] setting up the opening has brought engine in force mode! */
4839 /* Send 'go' if we are in a mode where machine should play. */
4840 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4841 (gameMode == TwoMachinesPlay ||
4843 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4845 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4846 SendToProgram("go\n", cps);
4847 if (appData.debugMode) {
4848 fprintf(debugFP, "(extra)\n");
4851 setboardSpoiledMachineBlack = 0;
4855 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4857 int fromX, fromY, toX, toY;
4860 char user_move[MSG_SIZ];
4864 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4865 (int)moveType, fromX, fromY, toX, toY);
4866 DisplayError(user_move + strlen("say "), 0);
4868 case WhiteKingSideCastle:
4869 case BlackKingSideCastle:
4870 case WhiteQueenSideCastleWild:
4871 case BlackQueenSideCastleWild:
4873 case WhiteHSideCastleFR:
4874 case BlackHSideCastleFR:
4876 snprintf(user_move, MSG_SIZ, "o-o\n");
4878 case WhiteQueenSideCastle:
4879 case BlackQueenSideCastle:
4880 case WhiteKingSideCastleWild:
4881 case BlackKingSideCastleWild:
4883 case WhiteASideCastleFR:
4884 case BlackASideCastleFR:
4886 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4888 case WhiteNonPromotion:
4889 case BlackNonPromotion:
4890 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4892 case WhitePromotion:
4893 case BlackPromotion:
4894 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4895 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4896 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4897 PieceToChar(WhiteFerz));
4898 else if(gameInfo.variant == VariantGreat)
4899 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4900 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4901 PieceToChar(WhiteMan));
4903 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4904 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4910 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4911 ToUpper(PieceToChar((ChessSquare) fromX)),
4912 AAA + toX, ONE + toY);
4914 case IllegalMove: /* could be a variant we don't quite understand */
4915 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4917 case WhiteCapturesEnPassant:
4918 case BlackCapturesEnPassant:
4919 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4920 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4923 SendToICS(user_move);
4924 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4925 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4930 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4931 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4932 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4933 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4934 DisplayError("You cannot do this while you are playing or observing", 0);
4937 if(gameMode != IcsExamining) { // is this ever not the case?
4938 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4940 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4941 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4942 } else { // on FICS we must first go to general examine mode
4943 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4945 if(gameInfo.variant != VariantNormal) {
4946 // try figure out wild number, as xboard names are not always valid on ICS
4947 for(i=1; i<=36; i++) {
4948 snprintf(buf, MSG_SIZ, "wild/%d", i);
4949 if(StringToVariant(buf) == gameInfo.variant) break;
4951 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4952 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4953 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4954 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4955 SendToICS(ics_prefix);
4957 if(startedFromSetupPosition || backwardMostMove != 0) {
4958 fen = PositionToFEN(backwardMostMove, NULL);
4959 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4960 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4962 } else { // FICS: everything has to set by separate bsetup commands
4963 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4964 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4966 if(!WhiteOnMove(backwardMostMove)) {
4967 SendToICS("bsetup tomove black\n");
4969 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4970 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4972 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4973 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4975 i = boards[backwardMostMove][EP_STATUS];
4976 if(i >= 0) { // set e.p.
4977 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4983 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4984 SendToICS("bsetup done\n"); // switch to normal examining.
4986 for(i = backwardMostMove; i<last; i++) {
4988 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4991 SendToICS(ics_prefix);
4992 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4996 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5001 if (rf == DROP_RANK) {
5002 sprintf(move, "%c@%c%c\n",
5003 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5005 if (promoChar == 'x' || promoChar == NULLCHAR) {
5006 sprintf(move, "%c%c%c%c\n",
5007 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5009 sprintf(move, "%c%c%c%c%c\n",
5010 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5016 ProcessICSInitScript(f)
5021 while (fgets(buf, MSG_SIZ, f)) {
5022 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5029 static int lastX, lastY, selectFlag, dragging;
5034 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5035 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5036 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5037 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5038 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5039 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5042 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5043 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5044 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5045 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5047 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5048 appData.testLegality && (promoSweep == king ||
5049 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5050 ChangeDragPiece(promoSweep);
5053 int PromoScroll(int x, int y)
5057 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5058 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5059 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5060 if(!step) return FALSE;
5061 lastX = x; lastY = y;
5062 if((promoSweep < BlackPawn) == flipView) step = -step;
5063 if(step > 0) selectFlag = 1;
5064 if(!selectFlag) Sweep(step);
5071 ChessSquare piece = boards[currentMove][toY][toX];
5074 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5075 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5076 if(!step) step = -1;
5077 } while(PieceToChar(pieceSweep) == '.');
5078 boards[currentMove][toY][toX] = pieceSweep;
5079 DrawPosition(FALSE, boards[currentMove]);
5080 boards[currentMove][toY][toX] = piece;
5082 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5084 AlphaRank(char *move, int n)
5086 // char *p = move, c; int x, y;
5088 if (appData.debugMode) {
5089 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5093 move[2]>='0' && move[2]<='9' &&
5094 move[3]>='a' && move[3]<='x' ) {
5096 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5097 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5099 if(move[0]>='0' && move[0]<='9' &&
5100 move[1]>='a' && move[1]<='x' &&
5101 move[2]>='0' && move[2]<='9' &&
5102 move[3]>='a' && move[3]<='x' ) {
5103 /* input move, Shogi -> normal */
5104 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5105 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5106 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5107 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5110 move[3]>='0' && move[3]<='9' &&
5111 move[2]>='a' && move[2]<='x' ) {
5113 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5114 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5117 move[0]>='a' && move[0]<='x' &&
5118 move[3]>='0' && move[3]<='9' &&
5119 move[2]>='a' && move[2]<='x' ) {
5120 /* output move, normal -> Shogi */
5121 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5122 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5123 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5124 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5125 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5127 if (appData.debugMode) {
5128 fprintf(debugFP, " out = '%s'\n", move);
5132 char yy_textstr[8000];
5134 /* Parser for moves from gnuchess, ICS, or user typein box */
5136 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5139 ChessMove *moveType;
5140 int *fromX, *fromY, *toX, *toY;
5143 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5145 switch (*moveType) {
5146 case WhitePromotion:
5147 case BlackPromotion:
5148 case WhiteNonPromotion:
5149 case BlackNonPromotion:
5151 case WhiteCapturesEnPassant:
5152 case BlackCapturesEnPassant:
5153 case WhiteKingSideCastle:
5154 case WhiteQueenSideCastle:
5155 case BlackKingSideCastle:
5156 case BlackQueenSideCastle:
5157 case WhiteKingSideCastleWild:
5158 case WhiteQueenSideCastleWild:
5159 case BlackKingSideCastleWild:
5160 case BlackQueenSideCastleWild:
5161 /* Code added by Tord: */
5162 case WhiteHSideCastleFR:
5163 case WhiteASideCastleFR:
5164 case BlackHSideCastleFR:
5165 case BlackASideCastleFR:
5166 /* End of code added by Tord */
5167 case IllegalMove: /* bug or odd chess variant */
5168 *fromX = currentMoveString[0] - AAA;
5169 *fromY = currentMoveString[1] - ONE;
5170 *toX = currentMoveString[2] - AAA;
5171 *toY = currentMoveString[3] - ONE;
5172 *promoChar = currentMoveString[4];
5173 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5174 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5175 if (appData.debugMode) {
5176 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5178 *fromX = *fromY = *toX = *toY = 0;
5181 if (appData.testLegality) {
5182 return (*moveType != IllegalMove);
5184 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5185 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5190 *fromX = *moveType == WhiteDrop ?
5191 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5192 (int) CharToPiece(ToLower(currentMoveString[0]));
5194 *toX = currentMoveString[2] - AAA;
5195 *toY = currentMoveString[3] - ONE;
5196 *promoChar = NULLCHAR;
5200 case ImpossibleMove:
5210 if (appData.debugMode) {
5211 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5214 *fromX = *fromY = *toX = *toY = 0;
5215 *promoChar = NULLCHAR;
5222 ParsePV(char *pv, Boolean storeComments)
5223 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5224 int fromX, fromY, toX, toY; char promoChar;
5229 endPV = forwardMostMove;
5231 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5232 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5233 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5234 if(appData.debugMode){
5235 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);
5237 if(!valid && nr == 0 &&
5238 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5239 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5240 // Hande case where played move is different from leading PV move
5241 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5242 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5243 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5244 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5245 endPV += 2; // if position different, keep this
5246 moveList[endPV-1][0] = fromX + AAA;
5247 moveList[endPV-1][1] = fromY + ONE;
5248 moveList[endPV-1][2] = toX + AAA;
5249 moveList[endPV-1][3] = toY + ONE;
5250 parseList[endPV-1][0] = NULLCHAR;
5251 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5254 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5255 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5256 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5257 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5258 valid++; // allow comments in PV
5262 if(endPV+1 > framePtr) break; // no space, truncate
5265 CopyBoard(boards[endPV], boards[endPV-1]);
5266 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5267 moveList[endPV-1][0] = fromX + AAA;
5268 moveList[endPV-1][1] = fromY + ONE;
5269 moveList[endPV-1][2] = toX + AAA;
5270 moveList[endPV-1][3] = toY + ONE;
5271 moveList[endPV-1][4] = promoChar;
5272 moveList[endPV-1][5] = NULLCHAR;
5273 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5275 CoordsToAlgebraic(boards[endPV - 1],
5276 PosFlags(endPV - 1),
5277 fromY, fromX, toY, toX, promoChar,
5278 parseList[endPV - 1]);
5280 parseList[endPV-1][0] = NULLCHAR;
5282 currentMove = endPV;
5283 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5284 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5285 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5286 DrawPosition(TRUE, boards[currentMove]);
5290 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5295 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5296 lastX = x; lastY = y;
5297 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5299 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5300 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5302 do{ while(buf[index] && buf[index] != '\n') index++;
5303 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5305 ParsePV(buf+startPV, FALSE);
5306 *start = startPV; *end = index-1;
5311 LoadPV(int x, int y)
5312 { // called on right mouse click to load PV
5313 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5314 lastX = x; lastY = y;
5315 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5322 if(endPV < 0) return;
5324 currentMove = forwardMostMove;
5325 ClearPremoveHighlights();
5326 DrawPosition(TRUE, boards[currentMove]);
5330 MovePV(int x, int y, int h)
5331 { // step through PV based on mouse coordinates (called on mouse move)
5332 int margin = h>>3, step = 0;
5334 // we must somehow check if right button is still down (might be released off board!)
5335 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5336 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5337 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5339 lastX = x; lastY = y;
5341 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5342 if(endPV < 0) return;
5343 if(y < margin) step = 1; else
5344 if(y > h - margin) step = -1;
5345 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5346 currentMove += step;
5347 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5348 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5349 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5350 DrawPosition(FALSE, boards[currentMove]);
5354 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5355 // All positions will have equal probability, but the current method will not provide a unique
5356 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5362 int piecesLeft[(int)BlackPawn];
5363 int seed, nrOfShuffles;
5365 void GetPositionNumber()
5366 { // sets global variable seed
5369 seed = appData.defaultFrcPosition;
5370 if(seed < 0) { // randomize based on time for negative FRC position numbers
5371 for(i=0; i<50; i++) seed += random();
5372 seed = random() ^ random() >> 8 ^ random() << 8;
5373 if(seed<0) seed = -seed;
5377 int put(Board board, int pieceType, int rank, int n, int shade)
5378 // put the piece on the (n-1)-th empty squares of the given shade
5382 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5383 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5384 board[rank][i] = (ChessSquare) pieceType;
5385 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5387 piecesLeft[pieceType]--;
5395 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5396 // calculate where the next piece goes, (any empty square), and put it there
5400 i = seed % squaresLeft[shade];
5401 nrOfShuffles *= squaresLeft[shade];
5402 seed /= squaresLeft[shade];
5403 put(board, pieceType, rank, i, shade);
5406 void AddTwoPieces(Board board, int pieceType, int rank)
5407 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5409 int i, n=squaresLeft[ANY], j=n-1, k;
5411 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5412 i = seed % k; // pick one
5415 while(i >= j) i -= j--;
5416 j = n - 1 - j; i += j;
5417 put(board, pieceType, rank, j, ANY);
5418 put(board, pieceType, rank, i, ANY);
5421 void SetUpShuffle(Board board, int number)
5425 GetPositionNumber(); nrOfShuffles = 1;
5427 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5428 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5429 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5431 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5433 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5434 p = (int) board[0][i];
5435 if(p < (int) BlackPawn) piecesLeft[p] ++;
5436 board[0][i] = EmptySquare;
5439 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5440 // shuffles restricted to allow normal castling put KRR first
5441 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5442 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5443 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5444 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5445 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5446 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5447 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5448 put(board, WhiteRook, 0, 0, ANY);
5449 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5452 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5453 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5454 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5455 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5456 while(piecesLeft[p] >= 2) {
5457 AddOnePiece(board, p, 0, LITE);
5458 AddOnePiece(board, p, 0, DARK);
5460 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5463 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5464 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5465 // but we leave King and Rooks for last, to possibly obey FRC restriction
5466 if(p == (int)WhiteRook) continue;
5467 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5468 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5471 // now everything is placed, except perhaps King (Unicorn) and Rooks
5473 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5474 // Last King gets castling rights
5475 while(piecesLeft[(int)WhiteUnicorn]) {
5476 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5477 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5480 while(piecesLeft[(int)WhiteKing]) {
5481 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5482 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5487 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5488 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5491 // Only Rooks can be left; simply place them all
5492 while(piecesLeft[(int)WhiteRook]) {
5493 i = put(board, WhiteRook, 0, 0, ANY);
5494 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5497 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5499 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5502 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5503 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5506 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5509 int SetCharTable( char *table, const char * map )
5510 /* [HGM] moved here from winboard.c because of its general usefulness */
5511 /* Basically a safe strcpy that uses the last character as King */
5513 int result = FALSE; int NrPieces;
5515 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5516 && NrPieces >= 12 && !(NrPieces&1)) {
5517 int i; /* [HGM] Accept even length from 12 to 34 */
5519 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5520 for( i=0; i<NrPieces/2-1; i++ ) {
5522 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5524 table[(int) WhiteKing] = map[NrPieces/2-1];
5525 table[(int) BlackKing] = map[NrPieces-1];
5533 void Prelude(Board board)
5534 { // [HGM] superchess: random selection of exo-pieces
5535 int i, j, k; ChessSquare p;
5536 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5538 GetPositionNumber(); // use FRC position number
5540 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5541 SetCharTable(pieceToChar, appData.pieceToCharTable);
5542 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5543 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5546 j = seed%4; seed /= 4;
5547 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5548 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5549 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5550 j = seed%3 + (seed%3 >= j); seed /= 3;
5551 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5552 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5553 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5554 j = seed%3; seed /= 3;
5555 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5556 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5557 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5558 j = seed%2 + (seed%2 >= j); seed /= 2;
5559 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5560 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5561 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5562 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5563 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5564 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5565 put(board, exoPieces[0], 0, 0, ANY);
5566 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5570 InitPosition(redraw)
5573 ChessSquare (* pieces)[BOARD_FILES];
5574 int i, j, pawnRow, overrule,
5575 oldx = gameInfo.boardWidth,
5576 oldy = gameInfo.boardHeight,
5577 oldh = gameInfo.holdingsWidth;
5580 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5582 /* [AS] Initialize pv info list [HGM] and game status */
5584 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5585 pvInfoList[i].depth = 0;
5586 boards[i][EP_STATUS] = EP_NONE;
5587 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5590 initialRulePlies = 0; /* 50-move counter start */
5592 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5593 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5597 /* [HGM] logic here is completely changed. In stead of full positions */
5598 /* the initialized data only consist of the two backranks. The switch */
5599 /* selects which one we will use, which is than copied to the Board */
5600 /* initialPosition, which for the rest is initialized by Pawns and */
5601 /* empty squares. This initial position is then copied to boards[0], */
5602 /* possibly after shuffling, so that it remains available. */
5604 gameInfo.holdingsWidth = 0; /* default board sizes */
5605 gameInfo.boardWidth = 8;
5606 gameInfo.boardHeight = 8;
5607 gameInfo.holdingsSize = 0;
5608 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5609 for(i=0; i<BOARD_FILES-2; i++)
5610 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5611 initialPosition[EP_STATUS] = EP_NONE;
5612 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5613 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5614 SetCharTable(pieceNickName, appData.pieceNickNames);
5615 else SetCharTable(pieceNickName, "............");
5618 switch (gameInfo.variant) {
5619 case VariantFischeRandom:
5620 shuffleOpenings = TRUE;
5623 case VariantShatranj:
5624 pieces = ShatranjArray;
5625 nrCastlingRights = 0;
5626 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5629 pieces = makrukArray;
5630 nrCastlingRights = 0;
5631 startedFromSetupPosition = TRUE;
5632 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5634 case VariantTwoKings:
5635 pieces = twoKingsArray;
5637 case VariantCapaRandom:
5638 shuffleOpenings = TRUE;
5639 case VariantCapablanca:
5640 pieces = CapablancaArray;
5641 gameInfo.boardWidth = 10;
5642 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5645 pieces = GothicArray;
5646 gameInfo.boardWidth = 10;
5647 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5650 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5651 gameInfo.holdingsSize = 7;
5654 pieces = JanusArray;
5655 gameInfo.boardWidth = 10;
5656 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5657 nrCastlingRights = 6;
5658 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5659 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5660 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5661 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5662 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5663 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5666 pieces = FalconArray;
5667 gameInfo.boardWidth = 10;
5668 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5670 case VariantXiangqi:
5671 pieces = XiangqiArray;
5672 gameInfo.boardWidth = 9;
5673 gameInfo.boardHeight = 10;
5674 nrCastlingRights = 0;
5675 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5678 pieces = ShogiArray;
5679 gameInfo.boardWidth = 9;
5680 gameInfo.boardHeight = 9;
5681 gameInfo.holdingsSize = 7;
5682 nrCastlingRights = 0;
5683 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5685 case VariantCourier:
5686 pieces = CourierArray;
5687 gameInfo.boardWidth = 12;
5688 nrCastlingRights = 0;
5689 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5691 case VariantKnightmate:
5692 pieces = KnightmateArray;
5693 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5695 case VariantSpartan:
5696 pieces = SpartanArray;
5697 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5700 pieces = fairyArray;
5701 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5704 pieces = GreatArray;
5705 gameInfo.boardWidth = 10;
5706 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5707 gameInfo.holdingsSize = 8;
5711 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5712 gameInfo.holdingsSize = 8;
5713 startedFromSetupPosition = TRUE;
5715 case VariantCrazyhouse:
5716 case VariantBughouse:
5718 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5719 gameInfo.holdingsSize = 5;
5721 case VariantWildCastle:
5723 /* !!?shuffle with kings guaranteed to be on d or e file */
5724 shuffleOpenings = 1;
5726 case VariantNoCastle:
5728 nrCastlingRights = 0;
5729 /* !!?unconstrained back-rank shuffle */
5730 shuffleOpenings = 1;
5735 if(appData.NrFiles >= 0) {
5736 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5737 gameInfo.boardWidth = appData.NrFiles;
5739 if(appData.NrRanks >= 0) {
5740 gameInfo.boardHeight = appData.NrRanks;
5742 if(appData.holdingsSize >= 0) {
5743 i = appData.holdingsSize;
5744 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5745 gameInfo.holdingsSize = i;
5747 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5748 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5749 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5751 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5752 if(pawnRow < 1) pawnRow = 1;
5753 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5755 /* User pieceToChar list overrules defaults */
5756 if(appData.pieceToCharTable != NULL)
5757 SetCharTable(pieceToChar, appData.pieceToCharTable);
5759 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5761 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5762 s = (ChessSquare) 0; /* account holding counts in guard band */
5763 for( i=0; i<BOARD_HEIGHT; i++ )
5764 initialPosition[i][j] = s;
5766 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5767 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5768 initialPosition[pawnRow][j] = WhitePawn;
5769 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5770 if(gameInfo.variant == VariantXiangqi) {
5772 initialPosition[pawnRow][j] =
5773 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5774 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5775 initialPosition[2][j] = WhiteCannon;
5776 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5780 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5782 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5785 initialPosition[1][j] = WhiteBishop;
5786 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5788 initialPosition[1][j] = WhiteRook;
5789 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5792 if( nrCastlingRights == -1) {
5793 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5794 /* This sets default castling rights from none to normal corners */
5795 /* Variants with other castling rights must set them themselves above */
5796 nrCastlingRights = 6;
5798 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5799 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5800 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5801 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5802 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5803 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5806 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5807 if(gameInfo.variant == VariantGreat) { // promotion commoners
5808 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5809 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5810 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5811 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5813 if( gameInfo.variant == VariantSChess ) {
5814 initialPosition[1][0] = BlackMarshall;
5815 initialPosition[2][0] = BlackAngel;
5816 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5817 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5818 initialPosition[1][1] = initialPosition[2][1] =
5819 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5821 if (appData.debugMode) {
5822 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5824 if(shuffleOpenings) {
5825 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5826 startedFromSetupPosition = TRUE;
5828 if(startedFromPositionFile) {
5829 /* [HGM] loadPos: use PositionFile for every new game */
5830 CopyBoard(initialPosition, filePosition);
5831 for(i=0; i<nrCastlingRights; i++)
5832 initialRights[i] = filePosition[CASTLING][i];
5833 startedFromSetupPosition = TRUE;
5836 CopyBoard(boards[0], initialPosition);
5838 if(oldx != gameInfo.boardWidth ||
5839 oldy != gameInfo.boardHeight ||
5840 oldv != gameInfo.variant ||
5841 oldh != gameInfo.holdingsWidth
5843 InitDrawingSizes(-2 ,0);
5845 oldv = gameInfo.variant;
5847 DrawPosition(TRUE, boards[currentMove]);
5851 SendBoard(cps, moveNum)
5852 ChessProgramState *cps;
5855 char message[MSG_SIZ];
5857 if (cps->useSetboard) {
5858 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5859 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5860 SendToProgram(message, cps);
5866 /* Kludge to set black to move, avoiding the troublesome and now
5867 * deprecated "black" command.
5869 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5870 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5872 SendToProgram("edit\n", cps);
5873 SendToProgram("#\n", cps);
5874 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5875 bp = &boards[moveNum][i][BOARD_LEFT];
5876 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5877 if ((int) *bp < (int) BlackPawn) {
5878 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5880 if(message[0] == '+' || message[0] == '~') {
5881 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5882 PieceToChar((ChessSquare)(DEMOTED *bp)),
5885 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5886 message[1] = BOARD_RGHT - 1 - j + '1';
5887 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5889 SendToProgram(message, cps);
5894 SendToProgram("c\n", cps);
5895 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5896 bp = &boards[moveNum][i][BOARD_LEFT];
5897 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5898 if (((int) *bp != (int) EmptySquare)
5899 && ((int) *bp >= (int) BlackPawn)) {
5900 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5902 if(message[0] == '+' || message[0] == '~') {
5903 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5904 PieceToChar((ChessSquare)(DEMOTED *bp)),
5907 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5908 message[1] = BOARD_RGHT - 1 - j + '1';
5909 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5911 SendToProgram(message, cps);
5916 SendToProgram(".\n", cps);
5918 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5922 DefaultPromoChoice(int white)
5925 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5926 result = WhiteFerz; // no choice
5927 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5928 result= WhiteKing; // in Suicide Q is the last thing we want
5929 else if(gameInfo.variant == VariantSpartan)
5930 result = white ? WhiteQueen : WhiteAngel;
5931 else result = WhiteQueen;
5932 if(!white) result = WHITE_TO_BLACK result;
5936 static int autoQueen; // [HGM] oneclick
5939 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5941 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5942 /* [HGM] add Shogi promotions */
5943 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5948 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5949 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5951 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5952 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5955 piece = boards[currentMove][fromY][fromX];
5956 if(gameInfo.variant == VariantShogi) {
5957 promotionZoneSize = BOARD_HEIGHT/3;
5958 highestPromotingPiece = (int)WhiteFerz;
5959 } else if(gameInfo.variant == VariantMakruk) {
5960 promotionZoneSize = 3;
5963 // Treat Lance as Pawn when it is not representing Amazon
5964 if(gameInfo.variant != VariantSuper) {
5965 if(piece == WhiteLance) piece = WhitePawn; else
5966 if(piece == BlackLance) piece = BlackPawn;
5969 // next weed out all moves that do not touch the promotion zone at all
5970 if((int)piece >= BlackPawn) {
5971 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5973 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5975 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5976 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5979 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5981 // weed out mandatory Shogi promotions
5982 if(gameInfo.variant == VariantShogi) {
5983 if(piece >= BlackPawn) {
5984 if(toY == 0 && piece == BlackPawn ||
5985 toY == 0 && piece == BlackQueen ||
5986 toY <= 1 && piece == BlackKnight) {
5991 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5992 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5993 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6000 // weed out obviously illegal Pawn moves
6001 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6002 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6003 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6004 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6005 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6006 // note we are not allowed to test for valid (non-)capture, due to premove
6009 // we either have a choice what to promote to, or (in Shogi) whether to promote
6010 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6011 *promoChoice = PieceToChar(BlackFerz); // no choice
6014 // no sense asking what we must promote to if it is going to explode...
6015 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6016 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6019 // give caller the default choice even if we will not make it
6020 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6021 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6022 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6023 && gameInfo.variant != VariantShogi
6024 && gameInfo.variant != VariantSuper) return FALSE;
6025 if(autoQueen) return FALSE; // predetermined
6027 // suppress promotion popup on illegal moves that are not premoves
6028 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6029 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6030 if(appData.testLegality && !premove) {
6031 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6032 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6033 if(moveType != WhitePromotion && moveType != BlackPromotion)
6041 InPalace(row, column)
6043 { /* [HGM] for Xiangqi */
6044 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6045 column < (BOARD_WIDTH + 4)/2 &&
6046 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6051 PieceForSquare (x, y)
6055 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6058 return boards[currentMove][y][x];
6062 OKToStartUserMove(x, y)
6065 ChessSquare from_piece;
6068 if (matchMode) return FALSE;
6069 if (gameMode == EditPosition) return TRUE;
6071 if (x >= 0 && y >= 0)
6072 from_piece = boards[currentMove][y][x];
6074 from_piece = EmptySquare;
6076 if (from_piece == EmptySquare) return FALSE;
6078 white_piece = (int)from_piece >= (int)WhitePawn &&
6079 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6082 case PlayFromGameFile:
6084 case TwoMachinesPlay:
6092 case MachinePlaysWhite:
6093 case IcsPlayingBlack:
6094 if (appData.zippyPlay) return FALSE;
6096 DisplayMoveError(_("You are playing Black"));
6101 case MachinePlaysBlack:
6102 case IcsPlayingWhite:
6103 if (appData.zippyPlay) return FALSE;
6105 DisplayMoveError(_("You are playing White"));
6111 if (!white_piece && WhiteOnMove(currentMove)) {
6112 DisplayMoveError(_("It is White's turn"));
6115 if (white_piece && !WhiteOnMove(currentMove)) {
6116 DisplayMoveError(_("It is Black's turn"));
6119 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6120 /* Editing correspondence game history */
6121 /* Could disallow this or prompt for confirmation */
6126 case BeginningOfGame:
6127 if (appData.icsActive) return FALSE;
6128 if (!appData.noChessProgram) {
6130 DisplayMoveError(_("You are playing White"));
6137 if (!white_piece && WhiteOnMove(currentMove)) {
6138 DisplayMoveError(_("It is White's turn"));
6141 if (white_piece && !WhiteOnMove(currentMove)) {
6142 DisplayMoveError(_("It is Black's turn"));
6151 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6152 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6153 && gameMode != AnalyzeFile && gameMode != Training) {
6154 DisplayMoveError(_("Displayed position is not current"));
6161 OnlyMove(int *x, int *y, Boolean captures) {
6162 DisambiguateClosure cl;
6163 if (appData.zippyPlay) return FALSE;
6165 case MachinePlaysBlack:
6166 case IcsPlayingWhite:
6167 case BeginningOfGame:
6168 if(!WhiteOnMove(currentMove)) return FALSE;
6170 case MachinePlaysWhite:
6171 case IcsPlayingBlack:
6172 if(WhiteOnMove(currentMove)) return FALSE;
6179 cl.pieceIn = EmptySquare;
6184 cl.promoCharIn = NULLCHAR;
6185 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6186 if( cl.kind == NormalMove ||
6187 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6188 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6189 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6196 if(cl.kind != ImpossibleMove) return FALSE;
6197 cl.pieceIn = EmptySquare;
6202 cl.promoCharIn = NULLCHAR;
6203 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6204 if( cl.kind == NormalMove ||
6205 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6206 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6207 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6212 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6218 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6219 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6220 int lastLoadGameUseList = FALSE;
6221 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6222 ChessMove lastLoadGameStart = EndOfFile;
6225 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6226 int fromX, fromY, toX, toY;
6230 ChessSquare pdown, pup;
6232 /* Check if the user is playing in turn. This is complicated because we
6233 let the user "pick up" a piece before it is his turn. So the piece he
6234 tried to pick up may have been captured by the time he puts it down!
6235 Therefore we use the color the user is supposed to be playing in this
6236 test, not the color of the piece that is currently on the starting
6237 square---except in EditGame mode, where the user is playing both
6238 sides; fortunately there the capture race can't happen. (It can
6239 now happen in IcsExamining mode, but that's just too bad. The user
6240 will get a somewhat confusing message in that case.)
6244 case PlayFromGameFile:
6246 case TwoMachinesPlay:
6250 /* We switched into a game mode where moves are not accepted,
6251 perhaps while the mouse button was down. */
6254 case MachinePlaysWhite:
6255 /* User is moving for Black */
6256 if (WhiteOnMove(currentMove)) {
6257 DisplayMoveError(_("It is White's turn"));
6262 case MachinePlaysBlack:
6263 /* User is moving for White */
6264 if (!WhiteOnMove(currentMove)) {
6265 DisplayMoveError(_("It is Black's turn"));
6272 case BeginningOfGame:
6275 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6276 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6277 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6278 /* User is moving for Black */
6279 if (WhiteOnMove(currentMove)) {
6280 DisplayMoveError(_("It is White's turn"));
6284 /* User is moving for White */
6285 if (!WhiteOnMove(currentMove)) {
6286 DisplayMoveError(_("It is Black's turn"));
6292 case IcsPlayingBlack:
6293 /* User is moving for Black */
6294 if (WhiteOnMove(currentMove)) {
6295 if (!appData.premove) {
6296 DisplayMoveError(_("It is White's turn"));
6297 } else if (toX >= 0 && toY >= 0) {
6300 premoveFromX = fromX;
6301 premoveFromY = fromY;
6302 premovePromoChar = promoChar;
6304 if (appData.debugMode)
6305 fprintf(debugFP, "Got premove: fromX %d,"
6306 "fromY %d, toX %d, toY %d\n",
6307 fromX, fromY, toX, toY);
6313 case IcsPlayingWhite:
6314 /* User is moving for White */
6315 if (!WhiteOnMove(currentMove)) {
6316 if (!appData.premove) {
6317 DisplayMoveError(_("It is Black's turn"));
6318 } else if (toX >= 0 && toY >= 0) {
6321 premoveFromX = fromX;
6322 premoveFromY = fromY;
6323 premovePromoChar = promoChar;
6325 if (appData.debugMode)
6326 fprintf(debugFP, "Got premove: fromX %d,"
6327 "fromY %d, toX %d, toY %d\n",
6328 fromX, fromY, toX, toY);
6338 /* EditPosition, empty square, or different color piece;
6339 click-click move is possible */
6340 if (toX == -2 || toY == -2) {
6341 boards[0][fromY][fromX] = EmptySquare;
6342 DrawPosition(FALSE, boards[currentMove]);
6344 } else if (toX >= 0 && toY >= 0) {
6345 boards[0][toY][toX] = boards[0][fromY][fromX];
6346 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6347 if(boards[0][fromY][0] != EmptySquare) {
6348 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6349 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6352 if(fromX == BOARD_RGHT+1) {
6353 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6354 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6355 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6358 boards[0][fromY][fromX] = EmptySquare;
6359 DrawPosition(FALSE, boards[currentMove]);
6365 if(toX < 0 || toY < 0) return;
6366 pdown = boards[currentMove][fromY][fromX];
6367 pup = boards[currentMove][toY][toX];
6369 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6370 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6371 if( pup != EmptySquare ) return;
6372 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6373 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6374 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6375 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6376 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6377 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6378 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6382 /* [HGM] always test for legality, to get promotion info */
6383 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6384 fromY, fromX, toY, toX, promoChar);
6385 /* [HGM] but possibly ignore an IllegalMove result */
6386 if (appData.testLegality) {
6387 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6388 DisplayMoveError(_("Illegal move"));
6393 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6396 /* Common tail of UserMoveEvent and DropMenuEvent */
6398 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6400 int fromX, fromY, toX, toY;
6401 /*char*/int promoChar;
6405 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6406 // [HGM] superchess: suppress promotions to non-available piece
6407 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6408 if(WhiteOnMove(currentMove)) {
6409 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6411 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6415 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6416 move type in caller when we know the move is a legal promotion */
6417 if(moveType == NormalMove && promoChar)
6418 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6420 /* [HGM] <popupFix> The following if has been moved here from
6421 UserMoveEvent(). Because it seemed to belong here (why not allow
6422 piece drops in training games?), and because it can only be
6423 performed after it is known to what we promote. */
6424 if (gameMode == Training) {
6425 /* compare the move played on the board to the next move in the
6426 * game. If they match, display the move and the opponent's response.
6427 * If they don't match, display an error message.
6431 CopyBoard(testBoard, boards[currentMove]);
6432 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6434 if (CompareBoards(testBoard, boards[currentMove+1])) {
6435 ForwardInner(currentMove+1);
6437 /* Autoplay the opponent's response.
6438 * if appData.animate was TRUE when Training mode was entered,
6439 * the response will be animated.
6441 saveAnimate = appData.animate;
6442 appData.animate = animateTraining;
6443 ForwardInner(currentMove+1);
6444 appData.animate = saveAnimate;
6446 /* check for the end of the game */
6447 if (currentMove >= forwardMostMove) {
6448 gameMode = PlayFromGameFile;
6450 SetTrainingModeOff();
6451 DisplayInformation(_("End of game"));
6454 DisplayError(_("Incorrect move"), 0);
6459 /* Ok, now we know that the move is good, so we can kill
6460 the previous line in Analysis Mode */
6461 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6462 && currentMove < forwardMostMove) {
6463 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6464 else forwardMostMove = currentMove;
6467 /* If we need the chess program but it's dead, restart it */
6468 ResurrectChessProgram();
6470 /* A user move restarts a paused game*/
6474 thinkOutput[0] = NULLCHAR;
6476 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6478 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6479 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6483 if (gameMode == BeginningOfGame) {
6484 if (appData.noChessProgram) {
6485 gameMode = EditGame;
6489 gameMode = MachinePlaysBlack;
6492 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6494 if (first.sendName) {
6495 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6496 SendToProgram(buf, &first);
6503 /* Relay move to ICS or chess engine */
6504 if (appData.icsActive) {
6505 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6506 gameMode == IcsExamining) {
6507 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6508 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6510 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6512 // also send plain move, in case ICS does not understand atomic claims
6513 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6517 if (first.sendTime && (gameMode == BeginningOfGame ||
6518 gameMode == MachinePlaysWhite ||
6519 gameMode == MachinePlaysBlack)) {
6520 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6522 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6523 // [HGM] book: if program might be playing, let it use book
6524 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6525 first.maybeThinking = TRUE;
6526 } else SendMoveToProgram(forwardMostMove-1, &first);
6527 if (currentMove == cmailOldMove + 1) {
6528 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6532 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6536 if(appData.testLegality)
6537 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6543 if (WhiteOnMove(currentMove)) {
6544 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6546 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6550 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6555 case MachinePlaysBlack:
6556 case MachinePlaysWhite:
6557 /* disable certain menu options while machine is thinking */
6558 SetMachineThinkingEnables();
6565 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6566 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6568 if(bookHit) { // [HGM] book: simulate book reply
6569 static char bookMove[MSG_SIZ]; // a bit generous?
6571 programStats.nodes = programStats.depth = programStats.time =
6572 programStats.score = programStats.got_only_move = 0;
6573 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6575 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6576 strcat(bookMove, bookHit);
6577 HandleMachineMove(bookMove, &first);
6583 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6590 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6591 Markers *m = (Markers *) closure;
6592 if(rf == fromY && ff == fromX)
6593 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6594 || kind == WhiteCapturesEnPassant
6595 || kind == BlackCapturesEnPassant);
6596 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6600 MarkTargetSquares(int clear)
6603 if(!appData.markers || !appData.highlightDragging ||
6604 !appData.testLegality || gameMode == EditPosition) return;
6606 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6609 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6610 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6611 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6613 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6616 DrawPosition(TRUE, NULL);
6620 Explode(Board board, int fromX, int fromY, int toX, int toY)
6622 if(gameInfo.variant == VariantAtomic &&
6623 (board[toY][toX] != EmptySquare || // capture?
6624 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6625 board[fromY][fromX] == BlackPawn )
6627 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6633 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6635 int CanPromote(ChessSquare piece, int y)
6637 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6638 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6639 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6640 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6641 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6642 gameInfo.variant == VariantMakruk) return FALSE;
6643 return (piece == BlackPawn && y == 1 ||
6644 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6645 piece == BlackLance && y == 1 ||
6646 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6649 void LeftClick(ClickType clickType, int xPix, int yPix)
6652 Boolean saveAnimate;
6653 static int second = 0, promotionChoice = 0, clearFlag = 0;
6654 char promoChoice = NULLCHAR;
6657 if(appData.seekGraph && appData.icsActive && loggedOn &&
6658 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6659 SeekGraphClick(clickType, xPix, yPix, 0);
6663 if (clickType == Press) ErrorPopDown();
6664 MarkTargetSquares(1);
6666 x = EventToSquare(xPix, BOARD_WIDTH);
6667 y = EventToSquare(yPix, BOARD_HEIGHT);
6668 if (!flipView && y >= 0) {
6669 y = BOARD_HEIGHT - 1 - y;
6671 if (flipView && x >= 0) {
6672 x = BOARD_WIDTH - 1 - x;
6675 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6676 defaultPromoChoice = promoSweep;
6677 promoSweep = EmptySquare; // terminate sweep
6678 promoDefaultAltered = TRUE;
6679 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6682 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6683 if(clickType == Release) return; // ignore upclick of click-click destination
6684 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6685 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6686 if(gameInfo.holdingsWidth &&
6687 (WhiteOnMove(currentMove)
6688 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6689 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6690 // click in right holdings, for determining promotion piece
6691 ChessSquare p = boards[currentMove][y][x];
6692 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6693 if(p != EmptySquare) {
6694 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6699 DrawPosition(FALSE, boards[currentMove]);
6703 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6704 if(clickType == Press
6705 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6706 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6707 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6710 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6711 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6713 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6714 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6715 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6716 defaultPromoChoice = DefaultPromoChoice(side);
6719 autoQueen = appData.alwaysPromoteToQueen;
6723 gatingPiece = EmptySquare;
6724 if (clickType != Press) {
6725 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6726 DragPieceEnd(xPix, yPix); dragging = 0;
6727 DrawPosition(FALSE, NULL);
6731 fromX = x; fromY = y;
6732 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6733 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6734 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6736 if (OKToStartUserMove(fromX, fromY)) {
6738 MarkTargetSquares(0);
6739 DragPieceBegin(xPix, yPix); dragging = 1;
6740 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6741 promoSweep = defaultPromoChoice;
6742 selectFlag = 0; lastX = xPix; lastY = yPix;
6743 Sweep(0); // Pawn that is going to promote: preview promotion piece
6744 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6746 if (appData.highlightDragging) {
6747 SetHighlights(fromX, fromY, -1, -1);
6749 } else fromX = fromY = -1;
6755 if (clickType == Press && gameMode != EditPosition) {
6760 // ignore off-board to clicks
6761 if(y < 0 || x < 0) return;
6763 /* Check if clicking again on the same color piece */
6764 fromP = boards[currentMove][fromY][fromX];
6765 toP = boards[currentMove][y][x];
6766 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6767 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6768 WhitePawn <= toP && toP <= WhiteKing &&
6769 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6770 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6771 (BlackPawn <= fromP && fromP <= BlackKing &&
6772 BlackPawn <= toP && toP <= BlackKing &&
6773 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6774 !(fromP == BlackKing && toP == BlackRook && frc))) {
6775 /* Clicked again on same color piece -- changed his mind */
6776 second = (x == fromX && y == fromY);
6777 promoDefaultAltered = FALSE;
6778 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6779 if (appData.highlightDragging) {
6780 SetHighlights(x, y, -1, -1);
6784 if (OKToStartUserMove(x, y)) {
6785 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6786 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6787 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6788 gatingPiece = boards[currentMove][fromY][fromX];
6789 else gatingPiece = EmptySquare;
6791 fromY = y; dragging = 1;
6792 MarkTargetSquares(0);
6793 DragPieceBegin(xPix, yPix);
6794 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6795 promoSweep = defaultPromoChoice;
6796 selectFlag = 0; lastX = xPix; lastY = yPix;
6797 Sweep(0); // Pawn that is going to promote: preview promotion piece
6801 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6804 // ignore clicks on holdings
6805 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6808 if (clickType == Release && x == fromX && y == fromY) {
6809 DragPieceEnd(xPix, yPix); dragging = 0;
6811 // a deferred attempt to click-click move an empty square on top of a piece
6812 boards[currentMove][y][x] = EmptySquare;
6814 DrawPosition(FALSE, boards[currentMove]);
6815 fromX = fromY = -1; clearFlag = 0;
6818 if (appData.animateDragging) {
6819 /* Undo animation damage if any */
6820 DrawPosition(FALSE, NULL);
6823 /* Second up/down in same square; just abort move */
6826 gatingPiece = EmptySquare;
6829 ClearPremoveHighlights();
6831 /* First upclick in same square; start click-click mode */
6832 SetHighlights(x, y, -1, -1);
6839 /* we now have a different from- and (possibly off-board) to-square */
6840 /* Completed move */
6843 saveAnimate = appData.animate;
6844 if (clickType == Press) {
6845 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6846 // must be Edit Position mode with empty-square selected
6847 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6848 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6851 /* Finish clickclick move */
6852 if (appData.animate || appData.highlightLastMove) {
6853 SetHighlights(fromX, fromY, toX, toY);
6858 /* Finish drag move */
6859 if (appData.highlightLastMove) {
6860 SetHighlights(fromX, fromY, toX, toY);
6864 DragPieceEnd(xPix, yPix); dragging = 0;
6865 /* Don't animate move and drag both */
6866 appData.animate = FALSE;
6869 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6870 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6871 ChessSquare piece = boards[currentMove][fromY][fromX];
6872 if(gameMode == EditPosition && piece != EmptySquare &&
6873 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6876 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6877 n = PieceToNumber(piece - (int)BlackPawn);
6878 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6879 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6880 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6882 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6883 n = PieceToNumber(piece);
6884 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6885 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6886 boards[currentMove][n][BOARD_WIDTH-2]++;
6888 boards[currentMove][fromY][fromX] = EmptySquare;
6892 DrawPosition(TRUE, boards[currentMove]);
6896 // off-board moves should not be highlighted
6897 if(x < 0 || y < 0) ClearHighlights();
6899 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6901 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6902 SetHighlights(fromX, fromY, toX, toY);
6903 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6904 // [HGM] super: promotion to captured piece selected from holdings
6905 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6906 promotionChoice = TRUE;
6907 // kludge follows to temporarily execute move on display, without promoting yet
6908 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6909 boards[currentMove][toY][toX] = p;
6910 DrawPosition(FALSE, boards[currentMove]);
6911 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6912 boards[currentMove][toY][toX] = q;
6913 DisplayMessage("Click in holdings to choose piece", "");
6918 int oldMove = currentMove;
6919 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6920 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6921 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6922 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6923 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6924 DrawPosition(TRUE, boards[currentMove]);
6927 appData.animate = saveAnimate;
6928 if (appData.animate || appData.animateDragging) {
6929 /* Undo animation damage if needed */
6930 DrawPosition(FALSE, NULL);
6934 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6935 { // front-end-free part taken out of PieceMenuPopup
6936 int whichMenu; int xSqr, ySqr;
6938 if(seekGraphUp) { // [HGM] seekgraph
6939 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6940 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6944 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6945 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6946 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6947 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6948 if(action == Press) {
6949 originalFlip = flipView;
6950 flipView = !flipView; // temporarily flip board to see game from partners perspective
6951 DrawPosition(TRUE, partnerBoard);
6952 DisplayMessage(partnerStatus, "");
6954 } else if(action == Release) {
6955 flipView = originalFlip;
6956 DrawPosition(TRUE, boards[currentMove]);
6962 xSqr = EventToSquare(x, BOARD_WIDTH);
6963 ySqr = EventToSquare(y, BOARD_HEIGHT);
6964 if (action == Release) {
6965 if(pieceSweep != EmptySquare) {
6966 EditPositionMenuEvent(pieceSweep, toX, toY);
6967 pieceSweep = EmptySquare;
6968 } else UnLoadPV(); // [HGM] pv
6970 if (action != Press) return -2; // return code to be ignored
6973 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6975 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6976 if (xSqr < 0 || ySqr < 0) return -1;
6977 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6978 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6979 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6980 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6984 if(!appData.icsEngineAnalyze) return -1;
6985 case IcsPlayingWhite:
6986 case IcsPlayingBlack:
6987 if(!appData.zippyPlay) goto noZip;
6990 case MachinePlaysWhite:
6991 case MachinePlaysBlack:
6992 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6993 if (!appData.dropMenu) {
6995 return 2; // flag front-end to grab mouse events
6997 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6998 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7001 if (xSqr < 0 || ySqr < 0) return -1;
7002 if (!appData.dropMenu || appData.testLegality &&
7003 gameInfo.variant != VariantBughouse &&
7004 gameInfo.variant != VariantCrazyhouse) return -1;
7005 whichMenu = 1; // drop menu
7011 if (((*fromX = xSqr) < 0) ||
7012 ((*fromY = ySqr) < 0)) {
7013 *fromX = *fromY = -1;
7017 *fromX = BOARD_WIDTH - 1 - *fromX;
7019 *fromY = BOARD_HEIGHT - 1 - *fromY;
7024 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7026 // char * hint = lastHint;
7027 FrontEndProgramStats stats;
7029 stats.which = cps == &first ? 0 : 1;
7030 stats.depth = cpstats->depth;
7031 stats.nodes = cpstats->nodes;
7032 stats.score = cpstats->score;
7033 stats.time = cpstats->time;
7034 stats.pv = cpstats->movelist;
7035 stats.hint = lastHint;
7036 stats.an_move_index = 0;
7037 stats.an_move_count = 0;
7039 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7040 stats.hint = cpstats->move_name;
7041 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7042 stats.an_move_count = cpstats->nr_moves;
7045 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
7047 SetProgramStats( &stats );
7050 #define MAXPLAYERS 500
7053 TourneyStandings(int display)
7055 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7056 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7057 char result, *p, *names[MAXPLAYERS];
7059 names[0] = p = strdup(appData.participants);
7060 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7062 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7064 while(result = appData.results[nr]) {
7065 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7066 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7067 wScore = bScore = 0;
7069 case '+': wScore = 2; break;
7070 case '-': bScore = 2; break;
7071 case '=': wScore = bScore = 1; break;
7073 case '*': return NULL; // tourney not finished
7081 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7082 for(w=0; w<nPlayers; w++) {
7084 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7085 ranking[w] = b; points[w] = bScore; score[b] = -2;
7087 p = malloc(nPlayers*34+1);
7088 for(w=0; w<nPlayers && w<display; w++)
7089 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7095 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7096 { // count all piece types
7098 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7099 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7100 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7103 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7104 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7105 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7106 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7107 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7108 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7113 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7115 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7116 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7118 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7119 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7120 if(myPawns == 2 && nMine == 3) // KPP
7121 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7122 if(myPawns == 1 && nMine == 2) // KP
7123 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7124 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7125 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7126 if(myPawns) return FALSE;
7127 if(pCnt[WhiteRook+side])
7128 return pCnt[BlackRook-side] ||
7129 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7130 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7131 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7132 if(pCnt[WhiteCannon+side]) {
7133 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7134 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7136 if(pCnt[WhiteKnight+side])
7137 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7142 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7144 VariantClass v = gameInfo.variant;
7146 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7147 if(v == VariantShatranj) return TRUE; // always winnable through baring
7148 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7149 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7151 if(v == VariantXiangqi) {
7152 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7154 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7155 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7156 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7157 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7158 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7159 if(stale) // we have at least one last-rank P plus perhaps C
7160 return majors // KPKX
7161 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7163 return pCnt[WhiteFerz+side] // KCAK
7164 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7165 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7166 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7168 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7169 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7171 if(nMine == 1) return FALSE; // bare King
7172 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
7173 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7174 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7175 // by now we have King + 1 piece (or multiple Bishops on the same color)
7176 if(pCnt[WhiteKnight+side])
7177 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7178 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7179 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7181 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7182 if(pCnt[WhiteAlfil+side])
7183 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7184 if(pCnt[WhiteWazir+side])
7185 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7192 Adjudicate(ChessProgramState *cps)
7193 { // [HGM] some adjudications useful with buggy engines
7194 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7195 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7196 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7197 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7198 int k, count = 0; static int bare = 1;
7199 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7200 Boolean canAdjudicate = !appData.icsActive;
7202 // most tests only when we understand the game, i.e. legality-checking on
7203 if( appData.testLegality )
7204 { /* [HGM] Some more adjudications for obstinate engines */
7205 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7206 static int moveCount = 6;
7208 char *reason = NULL;
7210 /* Count what is on board. */
7211 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7213 /* Some material-based adjudications that have to be made before stalemate test */
7214 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7215 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7216 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7217 if(canAdjudicate && appData.checkMates) {
7219 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7220 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7221 "Xboard adjudication: King destroyed", GE_XBOARD );
7226 /* Bare King in Shatranj (loses) or Losers (wins) */
7227 if( nrW == 1 || nrB == 1) {
7228 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7229 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7230 if(canAdjudicate && appData.checkMates) {
7232 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7233 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7234 "Xboard adjudication: Bare king", GE_XBOARD );
7238 if( gameInfo.variant == VariantShatranj && --bare < 0)
7240 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7241 if(canAdjudicate && appData.checkMates) {
7242 /* but only adjudicate if adjudication enabled */
7244 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7245 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7246 "Xboard adjudication: Bare king", GE_XBOARD );
7253 // don't wait for engine to announce game end if we can judge ourselves
7254 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7256 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7257 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7258 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7259 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7262 reason = "Xboard adjudication: 3rd check";
7263 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7273 reason = "Xboard adjudication: Stalemate";
7274 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7275 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7276 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7277 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7278 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7279 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7280 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7281 EP_CHECKMATE : EP_WINS);
7282 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7283 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7287 reason = "Xboard adjudication: Checkmate";
7288 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7292 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7294 result = GameIsDrawn; break;
7296 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7298 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7302 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7304 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7305 GameEnds( result, reason, GE_XBOARD );
7309 /* Next absolutely insufficient mating material. */
7310 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7311 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7312 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7314 /* always flag draws, for judging claims */
7315 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7317 if(canAdjudicate && appData.materialDraws) {
7318 /* but only adjudicate them if adjudication enabled */
7319 if(engineOpponent) {
7320 SendToProgram("force\n", engineOpponent); // suppress reply
7321 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7323 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7328 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7329 if(gameInfo.variant == VariantXiangqi ?
7330 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7332 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7333 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7334 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7335 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7337 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7338 { /* if the first 3 moves do not show a tactical win, declare draw */
7339 if(engineOpponent) {
7340 SendToProgram("force\n", engineOpponent); // suppress reply
7341 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7343 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7346 } else moveCount = 6;
7348 if (appData.debugMode) { int i;
7349 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7350 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7351 appData.drawRepeats);
7352 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7353 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7357 // Repetition draws and 50-move rule can be applied independently of legality testing
7359 /* Check for rep-draws */
7361 for(k = forwardMostMove-2;
7362 k>=backwardMostMove && k>=forwardMostMove-100 &&
7363 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7364 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7367 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7368 /* compare castling rights */
7369 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7370 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7371 rights++; /* King lost rights, while rook still had them */
7372 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7373 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7374 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7375 rights++; /* but at least one rook lost them */
7377 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7378 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7380 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7381 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7382 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7385 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7386 && appData.drawRepeats > 1) {
7387 /* adjudicate after user-specified nr of repeats */
7388 int result = GameIsDrawn;
7389 char *details = "XBoard adjudication: repetition draw";
7390 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7391 // [HGM] xiangqi: check for forbidden perpetuals
7392 int m, ourPerpetual = 1, hisPerpetual = 1;
7393 for(m=forwardMostMove; m>k; m-=2) {
7394 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7395 ourPerpetual = 0; // the current mover did not always check
7396 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7397 hisPerpetual = 0; // the opponent did not always check
7399 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7400 ourPerpetual, hisPerpetual);
7401 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7402 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7403 details = "Xboard adjudication: perpetual checking";
7405 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7406 break; // (or we would have caught him before). Abort repetition-checking loop.
7408 // Now check for perpetual chases
7409 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7410 hisPerpetual = PerpetualChase(k, forwardMostMove);
7411 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7412 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7413 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7414 details = "Xboard adjudication: perpetual chasing";
7416 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7417 break; // Abort repetition-checking loop.
7419 // if neither of us is checking or chasing all the time, or both are, it is draw
7421 if(engineOpponent) {
7422 SendToProgram("force\n", engineOpponent); // suppress reply
7423 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7425 GameEnds( result, details, GE_XBOARD );
7428 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7429 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7433 /* Now we test for 50-move draws. Determine ply count */
7434 count = forwardMostMove;
7435 /* look for last irreversble move */
7436 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7438 /* if we hit starting position, add initial plies */
7439 if( count == backwardMostMove )
7440 count -= initialRulePlies;
7441 count = forwardMostMove - count;
7442 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7443 // adjust reversible move counter for checks in Xiangqi
7444 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7445 if(i < backwardMostMove) i = backwardMostMove;
7446 while(i <= forwardMostMove) {
7447 lastCheck = inCheck; // check evasion does not count
7448 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7449 if(inCheck || lastCheck) count--; // check does not count
7454 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7455 /* this is used to judge if draw claims are legal */
7456 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7457 if(engineOpponent) {
7458 SendToProgram("force\n", engineOpponent); // suppress reply
7459 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7461 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7465 /* if draw offer is pending, treat it as a draw claim
7466 * when draw condition present, to allow engines a way to
7467 * claim draws before making their move to avoid a race
7468 * condition occurring after their move
7470 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7472 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7473 p = "Draw claim: 50-move rule";
7474 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7475 p = "Draw claim: 3-fold repetition";
7476 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7477 p = "Draw claim: insufficient mating material";
7478 if( p != NULL && canAdjudicate) {
7479 if(engineOpponent) {
7480 SendToProgram("force\n", engineOpponent); // suppress reply
7481 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7483 GameEnds( GameIsDrawn, p, GE_XBOARD );
7488 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7489 if(engineOpponent) {
7490 SendToProgram("force\n", engineOpponent); // suppress reply
7491 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7493 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7499 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7500 { // [HGM] book: this routine intercepts moves to simulate book replies
7501 char *bookHit = NULL;
7503 //first determine if the incoming move brings opponent into his book
7504 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7505 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7506 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7507 if(bookHit != NULL && !cps->bookSuspend) {
7508 // make sure opponent is not going to reply after receiving move to book position
7509 SendToProgram("force\n", cps);
7510 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7512 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7513 // now arrange restart after book miss
7515 // after a book hit we never send 'go', and the code after the call to this routine
7516 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7518 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7519 SendToProgram(buf, cps);
7520 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7521 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7522 SendToProgram("go\n", cps);
7523 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7524 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7525 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7526 SendToProgram("go\n", cps);
7527 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7529 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7533 ChessProgramState *savedState;
7534 void DeferredBookMove(void)
7536 if(savedState->lastPing != savedState->lastPong)
7537 ScheduleDelayedEvent(DeferredBookMove, 10);
7539 HandleMachineMove(savedMessage, savedState);
7543 HandleMachineMove(message, cps)
7545 ChessProgramState *cps;
7547 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7548 char realname[MSG_SIZ];
7549 int fromX, fromY, toX, toY;
7558 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7560 * Kludge to ignore BEL characters
7562 while (*message == '\007') message++;
7565 * [HGM] engine debug message: ignore lines starting with '#' character
7567 if(cps->debug && *message == '#') return;
7570 * Look for book output
7572 if (cps == &first && bookRequested) {
7573 if (message[0] == '\t' || message[0] == ' ') {
7574 /* Part of the book output is here; append it */
7575 strcat(bookOutput, message);
7576 strcat(bookOutput, " \n");
7578 } else if (bookOutput[0] != NULLCHAR) {
7579 /* All of book output has arrived; display it */
7580 char *p = bookOutput;
7581 while (*p != NULLCHAR) {
7582 if (*p == '\t') *p = ' ';
7585 DisplayInformation(bookOutput);
7586 bookRequested = FALSE;
7587 /* Fall through to parse the current output */
7592 * Look for machine move.
7594 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7595 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7597 /* This method is only useful on engines that support ping */
7598 if (cps->lastPing != cps->lastPong) {
7599 if (gameMode == BeginningOfGame) {
7600 /* Extra move from before last new; ignore */
7601 if (appData.debugMode) {
7602 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7605 if (appData.debugMode) {
7606 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7607 cps->which, gameMode);
7610 SendToProgram("undo\n", cps);
7616 case BeginningOfGame:
7617 /* Extra move from before last reset; ignore */
7618 if (appData.debugMode) {
7619 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7626 /* Extra move after we tried to stop. The mode test is
7627 not a reliable way of detecting this problem, but it's
7628 the best we can do on engines that don't support ping.
7630 if (appData.debugMode) {
7631 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7632 cps->which, gameMode);
7634 SendToProgram("undo\n", cps);
7637 case MachinePlaysWhite:
7638 case IcsPlayingWhite:
7639 machineWhite = TRUE;
7642 case MachinePlaysBlack:
7643 case IcsPlayingBlack:
7644 machineWhite = FALSE;
7647 case TwoMachinesPlay:
7648 machineWhite = (cps->twoMachinesColor[0] == 'w');
7651 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7652 if (appData.debugMode) {
7654 "Ignoring move out of turn by %s, gameMode %d"
7655 ", forwardMost %d\n",
7656 cps->which, gameMode, forwardMostMove);
7661 if (appData.debugMode) { int f = forwardMostMove;
7662 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7663 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7664 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7666 if(cps->alphaRank) AlphaRank(machineMove, 4);
7667 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7668 &fromX, &fromY, &toX, &toY, &promoChar)) {
7669 /* Machine move could not be parsed; ignore it. */
7670 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7671 machineMove, _(cps->which));
7672 DisplayError(buf1, 0);
7673 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7674 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7675 if (gameMode == TwoMachinesPlay) {
7676 GameEnds(machineWhite ? BlackWins : WhiteWins,
7682 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7683 /* So we have to redo legality test with true e.p. status here, */
7684 /* to make sure an illegal e.p. capture does not slip through, */
7685 /* to cause a forfeit on a justified illegal-move complaint */
7686 /* of the opponent. */
7687 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7689 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7690 fromY, fromX, toY, toX, promoChar);
7691 if (appData.debugMode) {
7693 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7694 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7695 fprintf(debugFP, "castling rights\n");
7697 if(moveType == IllegalMove) {
7698 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7699 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7700 GameEnds(machineWhite ? BlackWins : WhiteWins,
7703 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7704 /* [HGM] Kludge to handle engines that send FRC-style castling
7705 when they shouldn't (like TSCP-Gothic) */
7707 case WhiteASideCastleFR:
7708 case BlackASideCastleFR:
7710 currentMoveString[2]++;
7712 case WhiteHSideCastleFR:
7713 case BlackHSideCastleFR:
7715 currentMoveString[2]--;
7717 default: ; // nothing to do, but suppresses warning of pedantic compilers
7720 hintRequested = FALSE;
7721 lastHint[0] = NULLCHAR;
7722 bookRequested = FALSE;
7723 /* Program may be pondering now */
7724 cps->maybeThinking = TRUE;
7725 if (cps->sendTime == 2) cps->sendTime = 1;
7726 if (cps->offeredDraw) cps->offeredDraw--;
7728 /* [AS] Save move info*/
7729 pvInfoList[ forwardMostMove ].score = programStats.score;
7730 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7731 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7733 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7735 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7736 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7739 while( count < adjudicateLossPlies ) {
7740 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7743 score = -score; /* Flip score for winning side */
7746 if( score > adjudicateLossThreshold ) {
7753 if( count >= adjudicateLossPlies ) {
7754 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7756 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7757 "Xboard adjudication",
7764 if(Adjudicate(cps)) {
7765 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7766 return; // [HGM] adjudicate: for all automatic game ends
7770 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7772 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7773 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7775 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7777 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7779 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7780 char buf[3*MSG_SIZ];
7782 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7783 programStats.score / 100.,
7785 programStats.time / 100.,
7786 (unsigned int)programStats.nodes,
7787 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7788 programStats.movelist);
7790 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7795 /* [AS] Clear stats for next move */
7796 ClearProgramStats();
7797 thinkOutput[0] = NULLCHAR;
7798 hiddenThinkOutputState = 0;
7801 if (gameMode == TwoMachinesPlay) {
7802 /* [HGM] relaying draw offers moved to after reception of move */
7803 /* and interpreting offer as claim if it brings draw condition */
7804 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7805 SendToProgram("draw\n", cps->other);
7807 if (cps->other->sendTime) {
7808 SendTimeRemaining(cps->other,
7809 cps->other->twoMachinesColor[0] == 'w');
7811 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7812 if (firstMove && !bookHit) {
7814 if (cps->other->useColors) {
7815 SendToProgram(cps->other->twoMachinesColor, cps->other);
7817 SendToProgram("go\n", cps->other);
7819 cps->other->maybeThinking = TRUE;
7822 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7824 if (!pausing && appData.ringBellAfterMoves) {
7829 * Reenable menu items that were disabled while
7830 * machine was thinking
7832 if (gameMode != TwoMachinesPlay)
7833 SetUserThinkingEnables();
7835 // [HGM] book: after book hit opponent has received move and is now in force mode
7836 // force the book reply into it, and then fake that it outputted this move by jumping
7837 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7839 static char bookMove[MSG_SIZ]; // a bit generous?
7841 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7842 strcat(bookMove, bookHit);
7845 programStats.nodes = programStats.depth = programStats.time =
7846 programStats.score = programStats.got_only_move = 0;
7847 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7849 if(cps->lastPing != cps->lastPong) {
7850 savedMessage = message; // args for deferred call
7852 ScheduleDelayedEvent(DeferredBookMove, 10);
7861 /* Set special modes for chess engines. Later something general
7862 * could be added here; for now there is just one kludge feature,
7863 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7864 * when "xboard" is given as an interactive command.
7866 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7867 cps->useSigint = FALSE;
7868 cps->useSigterm = FALSE;
7870 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7871 ParseFeatures(message+8, cps);
7872 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7875 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7876 int dummy, s=6; char buf[MSG_SIZ];
7877 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7878 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7879 ParseFEN(boards[0], &dummy, message+s);
7880 DrawPosition(TRUE, boards[0]);
7881 startedFromSetupPosition = TRUE;
7884 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7885 * want this, I was asked to put it in, and obliged.
7887 if (!strncmp(message, "setboard ", 9)) {
7888 Board initial_position;
7890 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7892 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7893 DisplayError(_("Bad FEN received from engine"), 0);
7897 CopyBoard(boards[0], initial_position);
7898 initialRulePlies = FENrulePlies;
7899 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7900 else gameMode = MachinePlaysBlack;
7901 DrawPosition(FALSE, boards[currentMove]);
7907 * Look for communication commands
7909 if (!strncmp(message, "telluser ", 9)) {
7910 if(message[9] == '\\' && message[10] == '\\')
7911 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7912 DisplayNote(message + 9);
7915 if (!strncmp(message, "tellusererror ", 14)) {
7917 if(message[14] == '\\' && message[15] == '\\')
7918 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7919 DisplayError(message + 14, 0);
7922 if (!strncmp(message, "tellopponent ", 13)) {
7923 if (appData.icsActive) {
7925 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7929 DisplayNote(message + 13);
7933 if (!strncmp(message, "tellothers ", 11)) {
7934 if (appData.icsActive) {
7936 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7942 if (!strncmp(message, "tellall ", 8)) {
7943 if (appData.icsActive) {
7945 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7949 DisplayNote(message + 8);
7953 if (strncmp(message, "warning", 7) == 0) {
7954 /* Undocumented feature, use tellusererror in new code */
7955 DisplayError(message, 0);
7958 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7959 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7960 strcat(realname, " query");
7961 AskQuestion(realname, buf2, buf1, cps->pr);
7964 /* Commands from the engine directly to ICS. We don't allow these to be
7965 * sent until we are logged on. Crafty kibitzes have been known to
7966 * interfere with the login process.
7969 if (!strncmp(message, "tellics ", 8)) {
7970 SendToICS(message + 8);
7974 if (!strncmp(message, "tellicsnoalias ", 15)) {
7975 SendToICS(ics_prefix);
7976 SendToICS(message + 15);
7980 /* The following are for backward compatibility only */
7981 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7982 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7983 SendToICS(ics_prefix);
7989 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7993 * If the move is illegal, cancel it and redraw the board.
7994 * Also deal with other error cases. Matching is rather loose
7995 * here to accommodate engines written before the spec.
7997 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7998 strncmp(message, "Error", 5) == 0) {
7999 if (StrStr(message, "name") ||
8000 StrStr(message, "rating") || StrStr(message, "?") ||
8001 StrStr(message, "result") || StrStr(message, "board") ||
8002 StrStr(message, "bk") || StrStr(message, "computer") ||
8003 StrStr(message, "variant") || StrStr(message, "hint") ||
8004 StrStr(message, "random") || StrStr(message, "depth") ||
8005 StrStr(message, "accepted")) {
8008 if (StrStr(message, "protover")) {
8009 /* Program is responding to input, so it's apparently done
8010 initializing, and this error message indicates it is
8011 protocol version 1. So we don't need to wait any longer
8012 for it to initialize and send feature commands. */
8013 FeatureDone(cps, 1);
8014 cps->protocolVersion = 1;
8017 cps->maybeThinking = FALSE;
8019 if (StrStr(message, "draw")) {
8020 /* Program doesn't have "draw" command */
8021 cps->sendDrawOffers = 0;
8024 if (cps->sendTime != 1 &&
8025 (StrStr(message, "time") || StrStr(message, "otim"))) {
8026 /* Program apparently doesn't have "time" or "otim" command */
8030 if (StrStr(message, "analyze")) {
8031 cps->analysisSupport = FALSE;
8032 cps->analyzing = FALSE;
8034 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8035 DisplayError(buf2, 0);
8038 if (StrStr(message, "(no matching move)st")) {
8039 /* Special kludge for GNU Chess 4 only */
8040 cps->stKludge = TRUE;
8041 SendTimeControl(cps, movesPerSession, timeControl,
8042 timeIncrement, appData.searchDepth,
8046 if (StrStr(message, "(no matching move)sd")) {
8047 /* Special kludge for GNU Chess 4 only */
8048 cps->sdKludge = TRUE;
8049 SendTimeControl(cps, movesPerSession, timeControl,
8050 timeIncrement, appData.searchDepth,
8054 if (!StrStr(message, "llegal")) {
8057 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8058 gameMode == IcsIdle) return;
8059 if (forwardMostMove <= backwardMostMove) return;
8060 if (pausing) PauseEvent();
8061 if(appData.forceIllegal) {
8062 // [HGM] illegal: machine refused move; force position after move into it
8063 SendToProgram("force\n", cps);
8064 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8065 // we have a real problem now, as SendBoard will use the a2a3 kludge
8066 // when black is to move, while there might be nothing on a2 or black
8067 // might already have the move. So send the board as if white has the move.
8068 // But first we must change the stm of the engine, as it refused the last move
8069 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8070 if(WhiteOnMove(forwardMostMove)) {
8071 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8072 SendBoard(cps, forwardMostMove); // kludgeless board
8074 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8075 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8076 SendBoard(cps, forwardMostMove+1); // kludgeless board
8078 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8079 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8080 gameMode == TwoMachinesPlay)
8081 SendToProgram("go\n", cps);
8084 if (gameMode == PlayFromGameFile) {
8085 /* Stop reading this game file */
8086 gameMode = EditGame;
8089 /* [HGM] illegal-move claim should forfeit game when Xboard */
8090 /* only passes fully legal moves */
8091 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8092 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8093 "False illegal-move claim", GE_XBOARD );
8094 return; // do not take back move we tested as valid
8096 currentMove = forwardMostMove-1;
8097 DisplayMove(currentMove-1); /* before DisplayMoveError */
8098 SwitchClocks(forwardMostMove-1); // [HGM] race
8099 DisplayBothClocks();
8100 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8101 parseList[currentMove], _(cps->which));
8102 DisplayMoveError(buf1);
8103 DrawPosition(FALSE, boards[currentMove]);
8106 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8107 /* Program has a broken "time" command that
8108 outputs a string not ending in newline.
8114 * If chess program startup fails, exit with an error message.
8115 * Attempts to recover here are futile.
8117 if ((StrStr(message, "unknown host") != NULL)
8118 || (StrStr(message, "No remote directory") != NULL)
8119 || (StrStr(message, "not found") != NULL)
8120 || (StrStr(message, "No such file") != NULL)
8121 || (StrStr(message, "can't alloc") != NULL)
8122 || (StrStr(message, "Permission denied") != NULL)) {
8124 cps->maybeThinking = FALSE;
8125 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8126 _(cps->which), cps->program, cps->host, message);
8127 RemoveInputSource(cps->isr);
8128 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8129 if(cps == &first) appData.noChessProgram = TRUE;
8130 DisplayError(buf1, 0);
8136 * Look for hint output
8138 if (sscanf(message, "Hint: %s", buf1) == 1) {
8139 if (cps == &first && hintRequested) {
8140 hintRequested = FALSE;
8141 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8142 &fromX, &fromY, &toX, &toY, &promoChar)) {
8143 (void) CoordsToAlgebraic(boards[forwardMostMove],
8144 PosFlags(forwardMostMove),
8145 fromY, fromX, toY, toX, promoChar, buf1);
8146 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8147 DisplayInformation(buf2);
8149 /* Hint move could not be parsed!? */
8150 snprintf(buf2, sizeof(buf2),
8151 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8152 buf1, _(cps->which));
8153 DisplayError(buf2, 0);
8156 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8162 * Ignore other messages if game is not in progress
8164 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8165 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8168 * look for win, lose, draw, or draw offer
8170 if (strncmp(message, "1-0", 3) == 0) {
8171 char *p, *q, *r = "";
8172 p = strchr(message, '{');
8180 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8182 } else if (strncmp(message, "0-1", 3) == 0) {
8183 char *p, *q, *r = "";
8184 p = strchr(message, '{');
8192 /* Kludge for Arasan 4.1 bug */
8193 if (strcmp(r, "Black resigns") == 0) {
8194 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8197 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8199 } else if (strncmp(message, "1/2", 3) == 0) {
8200 char *p, *q, *r = "";
8201 p = strchr(message, '{');
8210 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8213 } else if (strncmp(message, "White resign", 12) == 0) {
8214 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8216 } else if (strncmp(message, "Black resign", 12) == 0) {
8217 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8219 } else if (strncmp(message, "White matches", 13) == 0 ||
8220 strncmp(message, "Black matches", 13) == 0 ) {
8221 /* [HGM] ignore GNUShogi noises */
8223 } else if (strncmp(message, "White", 5) == 0 &&
8224 message[5] != '(' &&
8225 StrStr(message, "Black") == NULL) {
8226 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8228 } else if (strncmp(message, "Black", 5) == 0 &&
8229 message[5] != '(') {
8230 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8232 } else if (strcmp(message, "resign") == 0 ||
8233 strcmp(message, "computer resigns") == 0) {
8235 case MachinePlaysBlack:
8236 case IcsPlayingBlack:
8237 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8239 case MachinePlaysWhite:
8240 case IcsPlayingWhite:
8241 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8243 case TwoMachinesPlay:
8244 if (cps->twoMachinesColor[0] == 'w')
8245 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8247 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8254 } else if (strncmp(message, "opponent mates", 14) == 0) {
8256 case MachinePlaysBlack:
8257 case IcsPlayingBlack:
8258 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8260 case MachinePlaysWhite:
8261 case IcsPlayingWhite:
8262 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8264 case TwoMachinesPlay:
8265 if (cps->twoMachinesColor[0] == 'w')
8266 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8268 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8275 } else if (strncmp(message, "computer mates", 14) == 0) {
8277 case MachinePlaysBlack:
8278 case IcsPlayingBlack:
8279 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8281 case MachinePlaysWhite:
8282 case IcsPlayingWhite:
8283 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8285 case TwoMachinesPlay:
8286 if (cps->twoMachinesColor[0] == 'w')
8287 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8289 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8296 } else if (strncmp(message, "checkmate", 9) == 0) {
8297 if (WhiteOnMove(forwardMostMove)) {
8298 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8300 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8303 } else if (strstr(message, "Draw") != NULL ||
8304 strstr(message, "game is a draw") != NULL) {
8305 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8307 } else if (strstr(message, "offer") != NULL &&
8308 strstr(message, "draw") != NULL) {
8310 if (appData.zippyPlay && first.initDone) {
8311 /* Relay offer to ICS */
8312 SendToICS(ics_prefix);
8313 SendToICS("draw\n");
8316 cps->offeredDraw = 2; /* valid until this engine moves twice */
8317 if (gameMode == TwoMachinesPlay) {
8318 if (cps->other->offeredDraw) {
8319 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8320 /* [HGM] in two-machine mode we delay relaying draw offer */
8321 /* until after we also have move, to see if it is really claim */
8323 } else if (gameMode == MachinePlaysWhite ||
8324 gameMode == MachinePlaysBlack) {
8325 if (userOfferedDraw) {
8326 DisplayInformation(_("Machine accepts your draw offer"));
8327 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8329 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8336 * Look for thinking output
8338 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8339 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8341 int plylev, mvleft, mvtot, curscore, time;
8342 char mvname[MOVE_LEN];
8346 int prefixHint = FALSE;
8347 mvname[0] = NULLCHAR;
8350 case MachinePlaysBlack:
8351 case IcsPlayingBlack:
8352 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8354 case MachinePlaysWhite:
8355 case IcsPlayingWhite:
8356 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8361 case IcsObserving: /* [DM] icsEngineAnalyze */
8362 if (!appData.icsEngineAnalyze) ignore = TRUE;
8364 case TwoMachinesPlay:
8365 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8375 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8377 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8378 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8380 if (plyext != ' ' && plyext != '\t') {
8384 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8385 if( cps->scoreIsAbsolute &&
8386 ( gameMode == MachinePlaysBlack ||
8387 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8388 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8389 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8390 !WhiteOnMove(currentMove)
8393 curscore = -curscore;
8397 tempStats.depth = plylev;
8398 tempStats.nodes = nodes;
8399 tempStats.time = time;
8400 tempStats.score = curscore;
8401 tempStats.got_only_move = 0;
8403 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8406 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8407 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8408 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8409 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8410 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8411 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8412 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8413 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8416 /* Buffer overflow protection */
8417 if (buf1[0] != NULLCHAR) {
8418 if (strlen(buf1) >= sizeof(tempStats.movelist)
8419 && appData.debugMode) {
8421 "PV is too long; using the first %u bytes.\n",
8422 (unsigned) sizeof(tempStats.movelist) - 1);
8425 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8427 sprintf(tempStats.movelist, " no PV\n");
8430 if (tempStats.seen_stat) {
8431 tempStats.ok_to_send = 1;
8434 if (strchr(tempStats.movelist, '(') != NULL) {
8435 tempStats.line_is_book = 1;
8436 tempStats.nr_moves = 0;
8437 tempStats.moves_left = 0;
8439 tempStats.line_is_book = 0;
8442 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8443 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8445 SendProgramStatsToFrontend( cps, &tempStats );
8448 [AS] Protect the thinkOutput buffer from overflow... this
8449 is only useful if buf1 hasn't overflowed first!
8451 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8453 (gameMode == TwoMachinesPlay ?
8454 ToUpper(cps->twoMachinesColor[0]) : ' '),
8455 ((double) curscore) / 100.0,
8456 prefixHint ? lastHint : "",
8457 prefixHint ? " " : "" );
8459 if( buf1[0] != NULLCHAR ) {
8460 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8462 if( strlen(buf1) > max_len ) {
8463 if( appData.debugMode) {
8464 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8466 buf1[max_len+1] = '\0';
8469 strcat( thinkOutput, buf1 );
8472 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8473 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8474 DisplayMove(currentMove - 1);
8478 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8479 /* crafty (9.25+) says "(only move) <move>"
8480 * if there is only 1 legal move
8482 sscanf(p, "(only move) %s", buf1);
8483 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8484 sprintf(programStats.movelist, "%s (only move)", buf1);
8485 programStats.depth = 1;
8486 programStats.nr_moves = 1;
8487 programStats.moves_left = 1;
8488 programStats.nodes = 1;
8489 programStats.time = 1;
8490 programStats.got_only_move = 1;
8492 /* Not really, but we also use this member to
8493 mean "line isn't going to change" (Crafty
8494 isn't searching, so stats won't change) */
8495 programStats.line_is_book = 1;
8497 SendProgramStatsToFrontend( cps, &programStats );
8499 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8500 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8501 DisplayMove(currentMove - 1);
8504 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8505 &time, &nodes, &plylev, &mvleft,
8506 &mvtot, mvname) >= 5) {
8507 /* The stat01: line is from Crafty (9.29+) in response
8508 to the "." command */
8509 programStats.seen_stat = 1;
8510 cps->maybeThinking = TRUE;
8512 if (programStats.got_only_move || !appData.periodicUpdates)
8515 programStats.depth = plylev;
8516 programStats.time = time;
8517 programStats.nodes = nodes;
8518 programStats.moves_left = mvleft;
8519 programStats.nr_moves = mvtot;
8520 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8521 programStats.ok_to_send = 1;
8522 programStats.movelist[0] = '\0';
8524 SendProgramStatsToFrontend( cps, &programStats );
8528 } else if (strncmp(message,"++",2) == 0) {
8529 /* Crafty 9.29+ outputs this */
8530 programStats.got_fail = 2;
8533 } else if (strncmp(message,"--",2) == 0) {
8534 /* Crafty 9.29+ outputs this */
8535 programStats.got_fail = 1;
8538 } else if (thinkOutput[0] != NULLCHAR &&
8539 strncmp(message, " ", 4) == 0) {
8540 unsigned message_len;
8543 while (*p && *p == ' ') p++;
8545 message_len = strlen( p );
8547 /* [AS] Avoid buffer overflow */
8548 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8549 strcat(thinkOutput, " ");
8550 strcat(thinkOutput, p);
8553 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8554 strcat(programStats.movelist, " ");
8555 strcat(programStats.movelist, p);
8558 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8559 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8560 DisplayMove(currentMove - 1);
8568 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8569 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8571 ChessProgramStats cpstats;
8573 if (plyext != ' ' && plyext != '\t') {
8577 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8578 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8579 curscore = -curscore;
8582 cpstats.depth = plylev;
8583 cpstats.nodes = nodes;
8584 cpstats.time = time;
8585 cpstats.score = curscore;
8586 cpstats.got_only_move = 0;
8587 cpstats.movelist[0] = '\0';
8589 if (buf1[0] != NULLCHAR) {
8590 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8593 cpstats.ok_to_send = 0;
8594 cpstats.line_is_book = 0;
8595 cpstats.nr_moves = 0;
8596 cpstats.moves_left = 0;
8598 SendProgramStatsToFrontend( cps, &cpstats );
8605 /* Parse a game score from the character string "game", and
8606 record it as the history of the current game. The game
8607 score is NOT assumed to start from the standard position.
8608 The display is not updated in any way.
8611 ParseGameHistory(game)
8615 int fromX, fromY, toX, toY, boardIndex;
8620 if (appData.debugMode)
8621 fprintf(debugFP, "Parsing game history: %s\n", game);
8623 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8624 gameInfo.site = StrSave(appData.icsHost);
8625 gameInfo.date = PGNDate();
8626 gameInfo.round = StrSave("-");
8628 /* Parse out names of players */
8629 while (*game == ' ') game++;
8631 while (*game != ' ') *p++ = *game++;
8633 gameInfo.white = StrSave(buf);
8634 while (*game == ' ') game++;
8636 while (*game != ' ' && *game != '\n') *p++ = *game++;
8638 gameInfo.black = StrSave(buf);
8641 boardIndex = blackPlaysFirst ? 1 : 0;
8644 yyboardindex = boardIndex;
8645 moveType = (ChessMove) Myylex();
8647 case IllegalMove: /* maybe suicide chess, etc. */
8648 if (appData.debugMode) {
8649 fprintf(debugFP, "Illegal 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 case WhitePromotion:
8654 case BlackPromotion:
8655 case WhiteNonPromotion:
8656 case BlackNonPromotion:
8658 case WhiteCapturesEnPassant:
8659 case BlackCapturesEnPassant:
8660 case WhiteKingSideCastle:
8661 case WhiteQueenSideCastle:
8662 case BlackKingSideCastle:
8663 case BlackQueenSideCastle:
8664 case WhiteKingSideCastleWild:
8665 case WhiteQueenSideCastleWild:
8666 case BlackKingSideCastleWild:
8667 case BlackQueenSideCastleWild:
8669 case WhiteHSideCastleFR:
8670 case WhiteASideCastleFR:
8671 case BlackHSideCastleFR:
8672 case BlackASideCastleFR:
8674 fromX = currentMoveString[0] - AAA;
8675 fromY = currentMoveString[1] - ONE;
8676 toX = currentMoveString[2] - AAA;
8677 toY = currentMoveString[3] - ONE;
8678 promoChar = currentMoveString[4];
8682 fromX = moveType == WhiteDrop ?
8683 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8684 (int) CharToPiece(ToLower(currentMoveString[0]));
8686 toX = currentMoveString[2] - AAA;
8687 toY = currentMoveString[3] - ONE;
8688 promoChar = NULLCHAR;
8692 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8693 if (appData.debugMode) {
8694 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8695 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8696 setbuf(debugFP, NULL);
8698 DisplayError(buf, 0);
8700 case ImpossibleMove:
8702 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8703 if (appData.debugMode) {
8704 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8705 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8706 setbuf(debugFP, NULL);
8708 DisplayError(buf, 0);
8711 if (boardIndex < backwardMostMove) {
8712 /* Oops, gap. How did that happen? */
8713 DisplayError(_("Gap in move list"), 0);
8716 backwardMostMove = blackPlaysFirst ? 1 : 0;
8717 if (boardIndex > forwardMostMove) {
8718 forwardMostMove = boardIndex;
8722 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8723 strcat(parseList[boardIndex-1], " ");
8724 strcat(parseList[boardIndex-1], yy_text);
8736 case GameUnfinished:
8737 if (gameMode == IcsExamining) {
8738 if (boardIndex < backwardMostMove) {
8739 /* Oops, gap. How did that happen? */
8742 backwardMostMove = blackPlaysFirst ? 1 : 0;
8745 gameInfo.result = moveType;
8746 p = strchr(yy_text, '{');
8747 if (p == NULL) p = strchr(yy_text, '(');
8750 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8752 q = strchr(p, *p == '{' ? '}' : ')');
8753 if (q != NULL) *q = NULLCHAR;
8756 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8757 gameInfo.resultDetails = StrSave(p);
8760 if (boardIndex >= forwardMostMove &&
8761 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8762 backwardMostMove = blackPlaysFirst ? 1 : 0;
8765 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8766 fromY, fromX, toY, toX, promoChar,
8767 parseList[boardIndex]);
8768 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8769 /* currentMoveString is set as a side-effect of yylex */
8770 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8771 strcat(moveList[boardIndex], "\n");
8773 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8774 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8780 if(gameInfo.variant != VariantShogi)
8781 strcat(parseList[boardIndex - 1], "+");
8785 strcat(parseList[boardIndex - 1], "#");
8792 /* Apply a move to the given board */
8794 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8795 int fromX, fromY, toX, toY;
8799 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8800 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8802 /* [HGM] compute & store e.p. status and castling rights for new position */
8803 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8805 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8806 oldEP = (signed char)board[EP_STATUS];
8807 board[EP_STATUS] = EP_NONE;
8809 if( board[toY][toX] != EmptySquare )
8810 board[EP_STATUS] = EP_CAPTURE;
8812 if (fromY == DROP_RANK) {
8814 piece = board[toY][toX] = (ChessSquare) fromX;
8818 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8819 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8820 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8822 if( board[fromY][fromX] == WhitePawn ) {
8823 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8824 board[EP_STATUS] = EP_PAWN_MOVE;
8826 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8827 gameInfo.variant != VariantBerolina || toX < fromX)
8828 board[EP_STATUS] = toX | berolina;
8829 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8830 gameInfo.variant != VariantBerolina || toX > fromX)
8831 board[EP_STATUS] = toX;
8834 if( board[fromY][fromX] == BlackPawn ) {
8835 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8836 board[EP_STATUS] = EP_PAWN_MOVE;
8837 if( toY-fromY== -2) {
8838 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8839 gameInfo.variant != VariantBerolina || toX < fromX)
8840 board[EP_STATUS] = toX | berolina;
8841 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8842 gameInfo.variant != VariantBerolina || toX > fromX)
8843 board[EP_STATUS] = toX;
8847 for(i=0; i<nrCastlingRights; i++) {
8848 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8849 board[CASTLING][i] == toX && castlingRank[i] == toY
8850 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8853 if (fromX == toX && fromY == toY) return;
8855 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8856 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8857 if(gameInfo.variant == VariantKnightmate)
8858 king += (int) WhiteUnicorn - (int) WhiteKing;
8860 /* Code added by Tord: */
8861 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8862 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8863 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8864 board[fromY][fromX] = EmptySquare;
8865 board[toY][toX] = EmptySquare;
8866 if((toX > fromX) != (piece == WhiteRook)) {
8867 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8869 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8871 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8872 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8873 board[fromY][fromX] = EmptySquare;
8874 board[toY][toX] = EmptySquare;
8875 if((toX > fromX) != (piece == BlackRook)) {
8876 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8878 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8880 /* End of code added by Tord */
8882 } else if (board[fromY][fromX] == king
8883 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8884 && toY == fromY && toX > fromX+1) {
8885 board[fromY][fromX] = EmptySquare;
8886 board[toY][toX] = king;
8887 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8888 board[fromY][BOARD_RGHT-1] = EmptySquare;
8889 } else if (board[fromY][fromX] == king
8890 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8891 && toY == fromY && toX < fromX-1) {
8892 board[fromY][fromX] = EmptySquare;
8893 board[toY][toX] = king;
8894 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8895 board[fromY][BOARD_LEFT] = EmptySquare;
8896 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8897 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8898 && toY >= BOARD_HEIGHT-promoRank
8900 /* white pawn promotion */
8901 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8902 if (board[toY][toX] == EmptySquare) {
8903 board[toY][toX] = WhiteQueen;
8905 if(gameInfo.variant==VariantBughouse ||
8906 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8907 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8908 board[fromY][fromX] = EmptySquare;
8909 } else if ((fromY == BOARD_HEIGHT-4)
8911 && gameInfo.variant != VariantXiangqi
8912 && gameInfo.variant != VariantBerolina
8913 && (board[fromY][fromX] == WhitePawn)
8914 && (board[toY][toX] == EmptySquare)) {
8915 board[fromY][fromX] = EmptySquare;
8916 board[toY][toX] = WhitePawn;
8917 captured = board[toY - 1][toX];
8918 board[toY - 1][toX] = EmptySquare;
8919 } else if ((fromY == BOARD_HEIGHT-4)
8921 && gameInfo.variant == VariantBerolina
8922 && (board[fromY][fromX] == WhitePawn)
8923 && (board[toY][toX] == EmptySquare)) {
8924 board[fromY][fromX] = EmptySquare;
8925 board[toY][toX] = WhitePawn;
8926 if(oldEP & EP_BEROLIN_A) {
8927 captured = board[fromY][fromX-1];
8928 board[fromY][fromX-1] = EmptySquare;
8929 }else{ captured = board[fromY][fromX+1];
8930 board[fromY][fromX+1] = EmptySquare;
8932 } else if (board[fromY][fromX] == king
8933 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8934 && toY == fromY && toX > fromX+1) {
8935 board[fromY][fromX] = EmptySquare;
8936 board[toY][toX] = king;
8937 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8938 board[fromY][BOARD_RGHT-1] = EmptySquare;
8939 } else if (board[fromY][fromX] == king
8940 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8941 && toY == fromY && toX < fromX-1) {
8942 board[fromY][fromX] = EmptySquare;
8943 board[toY][toX] = king;
8944 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8945 board[fromY][BOARD_LEFT] = EmptySquare;
8946 } else if (fromY == 7 && fromX == 3
8947 && board[fromY][fromX] == BlackKing
8948 && toY == 7 && toX == 5) {
8949 board[fromY][fromX] = EmptySquare;
8950 board[toY][toX] = BlackKing;
8951 board[fromY][7] = EmptySquare;
8952 board[toY][4] = BlackRook;
8953 } else if (fromY == 7 && fromX == 3
8954 && board[fromY][fromX] == BlackKing
8955 && toY == 7 && toX == 1) {
8956 board[fromY][fromX] = EmptySquare;
8957 board[toY][toX] = BlackKing;
8958 board[fromY][0] = EmptySquare;
8959 board[toY][2] = BlackRook;
8960 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8961 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8964 /* black pawn promotion */
8965 board[toY][toX] = CharToPiece(ToLower(promoChar));
8966 if (board[toY][toX] == EmptySquare) {
8967 board[toY][toX] = BlackQueen;
8969 if(gameInfo.variant==VariantBughouse ||
8970 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8971 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8972 board[fromY][fromX] = EmptySquare;
8973 } else if ((fromY == 3)
8975 && gameInfo.variant != VariantXiangqi
8976 && gameInfo.variant != VariantBerolina
8977 && (board[fromY][fromX] == BlackPawn)
8978 && (board[toY][toX] == EmptySquare)) {
8979 board[fromY][fromX] = EmptySquare;
8980 board[toY][toX] = BlackPawn;
8981 captured = board[toY + 1][toX];
8982 board[toY + 1][toX] = EmptySquare;
8983 } else if ((fromY == 3)
8985 && gameInfo.variant == VariantBerolina
8986 && (board[fromY][fromX] == BlackPawn)
8987 && (board[toY][toX] == EmptySquare)) {
8988 board[fromY][fromX] = EmptySquare;
8989 board[toY][toX] = BlackPawn;
8990 if(oldEP & EP_BEROLIN_A) {
8991 captured = board[fromY][fromX-1];
8992 board[fromY][fromX-1] = EmptySquare;
8993 }else{ captured = board[fromY][fromX+1];
8994 board[fromY][fromX+1] = EmptySquare;
8997 board[toY][toX] = board[fromY][fromX];
8998 board[fromY][fromX] = EmptySquare;
9002 if (gameInfo.holdingsWidth != 0) {
9004 /* !!A lot more code needs to be written to support holdings */
9005 /* [HGM] OK, so I have written it. Holdings are stored in the */
9006 /* penultimate board files, so they are automaticlly stored */
9007 /* in the game history. */
9008 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9009 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9010 /* Delete from holdings, by decreasing count */
9011 /* and erasing image if necessary */
9012 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9013 if(p < (int) BlackPawn) { /* white drop */
9014 p -= (int)WhitePawn;
9015 p = PieceToNumber((ChessSquare)p);
9016 if(p >= gameInfo.holdingsSize) p = 0;
9017 if(--board[p][BOARD_WIDTH-2] <= 0)
9018 board[p][BOARD_WIDTH-1] = EmptySquare;
9019 if((int)board[p][BOARD_WIDTH-2] < 0)
9020 board[p][BOARD_WIDTH-2] = 0;
9021 } else { /* black drop */
9022 p -= (int)BlackPawn;
9023 p = PieceToNumber((ChessSquare)p);
9024 if(p >= gameInfo.holdingsSize) p = 0;
9025 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9026 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9027 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9028 board[BOARD_HEIGHT-1-p][1] = 0;
9031 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9032 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9033 /* [HGM] holdings: Add to holdings, if holdings exist */
9034 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9035 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9036 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9039 if (p >= (int) BlackPawn) {
9040 p -= (int)BlackPawn;
9041 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9042 /* in Shogi restore piece to its original first */
9043 captured = (ChessSquare) (DEMOTED captured);
9046 p = PieceToNumber((ChessSquare)p);
9047 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9048 board[p][BOARD_WIDTH-2]++;
9049 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9051 p -= (int)WhitePawn;
9052 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9053 captured = (ChessSquare) (DEMOTED captured);
9056 p = PieceToNumber((ChessSquare)p);
9057 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9058 board[BOARD_HEIGHT-1-p][1]++;
9059 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9062 } else if (gameInfo.variant == VariantAtomic) {
9063 if (captured != EmptySquare) {
9065 for (y = toY-1; y <= toY+1; y++) {
9066 for (x = toX-1; x <= toX+1; x++) {
9067 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9068 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9069 board[y][x] = EmptySquare;
9073 board[toY][toX] = EmptySquare;
9076 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9077 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9079 if(promoChar == '+') {
9080 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9081 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9082 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9083 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9085 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9086 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9087 // [HGM] superchess: take promotion piece out of holdings
9088 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9089 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9090 if(!--board[k][BOARD_WIDTH-2])
9091 board[k][BOARD_WIDTH-1] = EmptySquare;
9093 if(!--board[BOARD_HEIGHT-1-k][1])
9094 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9100 /* Updates forwardMostMove */
9102 MakeMove(fromX, fromY, toX, toY, promoChar)
9103 int fromX, fromY, toX, toY;
9106 // forwardMostMove++; // [HGM] bare: moved downstream
9108 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9109 int timeLeft; static int lastLoadFlag=0; int king, piece;
9110 piece = boards[forwardMostMove][fromY][fromX];
9111 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9112 if(gameInfo.variant == VariantKnightmate)
9113 king += (int) WhiteUnicorn - (int) WhiteKing;
9114 if(forwardMostMove == 0) {
9116 fprintf(serverMoves, "%s;", second.tidy);
9117 fprintf(serverMoves, "%s;", first.tidy);
9118 if(!blackPlaysFirst)
9119 fprintf(serverMoves, "%s;", second.tidy);
9120 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9121 lastLoadFlag = loadFlag;
9123 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9124 // print castling suffix
9125 if( toY == fromY && piece == king ) {
9127 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9129 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9132 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9133 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9134 boards[forwardMostMove][toY][toX] == EmptySquare
9135 && fromX != toX && fromY != toY)
9136 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9138 if(promoChar != NULLCHAR)
9139 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9141 fprintf(serverMoves, "/%d/%d",
9142 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9143 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9144 else timeLeft = blackTimeRemaining/1000;
9145 fprintf(serverMoves, "/%d", timeLeft);
9147 fflush(serverMoves);
9150 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9151 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9155 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9156 if (commentList[forwardMostMove+1] != NULL) {
9157 free(commentList[forwardMostMove+1]);
9158 commentList[forwardMostMove+1] = NULL;
9160 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9161 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9162 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9163 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9164 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9165 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9166 gameInfo.result = GameUnfinished;
9167 if (gameInfo.resultDetails != NULL) {
9168 free(gameInfo.resultDetails);
9169 gameInfo.resultDetails = NULL;
9171 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9172 moveList[forwardMostMove - 1]);
9173 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9174 PosFlags(forwardMostMove - 1),
9175 fromY, fromX, toY, toX, promoChar,
9176 parseList[forwardMostMove - 1]);
9177 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9183 if(gameInfo.variant != VariantShogi)
9184 strcat(parseList[forwardMostMove - 1], "+");
9188 strcat(parseList[forwardMostMove - 1], "#");
9191 if (appData.debugMode) {
9192 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9197 /* Updates currentMove if not pausing */
9199 ShowMove(fromX, fromY, toX, toY)
9201 int instant = (gameMode == PlayFromGameFile) ?
9202 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9203 if(appData.noGUI) return;
9204 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9206 if (forwardMostMove == currentMove + 1) {
9207 AnimateMove(boards[forwardMostMove - 1],
9208 fromX, fromY, toX, toY);
9210 if (appData.highlightLastMove) {
9211 SetHighlights(fromX, fromY, toX, toY);
9214 currentMove = forwardMostMove;
9217 if (instant) return;
9219 DisplayMove(currentMove - 1);
9220 DrawPosition(FALSE, boards[currentMove]);
9221 DisplayBothClocks();
9222 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9225 void SendEgtPath(ChessProgramState *cps)
9226 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9227 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9229 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9232 char c, *q = name+1, *r, *s;
9234 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9235 while(*p && *p != ',') *q++ = *p++;
9237 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9238 strcmp(name, ",nalimov:") == 0 ) {
9239 // take nalimov path from the menu-changeable option first, if it is defined
9240 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9241 SendToProgram(buf,cps); // send egtbpath command for nalimov
9243 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9244 (s = StrStr(appData.egtFormats, name)) != NULL) {
9245 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9246 s = r = StrStr(s, ":") + 1; // beginning of path info
9247 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9248 c = *r; *r = 0; // temporarily null-terminate path info
9249 *--q = 0; // strip of trailig ':' from name
9250 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9252 SendToProgram(buf,cps); // send egtbpath command for this format
9254 if(*p == ',') p++; // read away comma to position for next format name
9259 InitChessProgram(cps, setup)
9260 ChessProgramState *cps;
9261 int setup; /* [HGM] needed to setup FRC opening position */
9263 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9264 if (appData.noChessProgram) return;
9265 hintRequested = FALSE;
9266 bookRequested = FALSE;
9268 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9269 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9270 if(cps->memSize) { /* [HGM] memory */
9271 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9272 SendToProgram(buf, cps);
9274 SendEgtPath(cps); /* [HGM] EGT */
9275 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9276 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9277 SendToProgram(buf, cps);
9280 SendToProgram(cps->initString, cps);
9281 if (gameInfo.variant != VariantNormal &&
9282 gameInfo.variant != VariantLoadable
9283 /* [HGM] also send variant if board size non-standard */
9284 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9286 char *v = VariantName(gameInfo.variant);
9287 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9288 /* [HGM] in protocol 1 we have to assume all variants valid */
9289 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9290 DisplayFatalError(buf, 0, 1);
9294 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9295 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9296 if( gameInfo.variant == VariantXiangqi )
9297 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9298 if( gameInfo.variant == VariantShogi )
9299 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9300 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9301 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9302 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9303 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9304 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9305 if( gameInfo.variant == VariantCourier )
9306 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9307 if( gameInfo.variant == VariantSuper )
9308 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9309 if( gameInfo.variant == VariantGreat )
9310 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9311 if( gameInfo.variant == VariantSChess )
9312 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9315 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9316 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9317 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9318 if(StrStr(cps->variants, b) == NULL) {
9319 // specific sized variant not known, check if general sizing allowed
9320 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9321 if(StrStr(cps->variants, "boardsize") == NULL) {
9322 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9323 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9324 DisplayFatalError(buf, 0, 1);
9327 /* [HGM] here we really should compare with the maximum supported board size */
9330 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9331 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9332 SendToProgram(buf, cps);
9334 currentlyInitializedVariant = gameInfo.variant;
9336 /* [HGM] send opening position in FRC to first engine */
9338 SendToProgram("force\n", cps);
9340 /* engine is now in force mode! Set flag to wake it up after first move. */
9341 setboardSpoiledMachineBlack = 1;
9345 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9346 SendToProgram(buf, cps);
9348 cps->maybeThinking = FALSE;
9349 cps->offeredDraw = 0;
9350 if (!appData.icsActive) {
9351 SendTimeControl(cps, movesPerSession, timeControl,
9352 timeIncrement, appData.searchDepth,
9355 if (appData.showThinking
9356 // [HGM] thinking: four options require thinking output to be sent
9357 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9359 SendToProgram("post\n", cps);
9361 SendToProgram("hard\n", cps);
9362 if (!appData.ponderNextMove) {
9363 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9364 it without being sure what state we are in first. "hard"
9365 is not a toggle, so that one is OK.
9367 SendToProgram("easy\n", cps);
9370 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9371 SendToProgram(buf, cps);
9373 cps->initDone = TRUE;
9378 StartChessProgram(cps)
9379 ChessProgramState *cps;
9384 if (appData.noChessProgram) return;
9385 cps->initDone = FALSE;
9387 if (strcmp(cps->host, "localhost") == 0) {
9388 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9389 } else if (*appData.remoteShell == NULLCHAR) {
9390 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9392 if (*appData.remoteUser == NULLCHAR) {
9393 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9396 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9397 cps->host, appData.remoteUser, cps->program);
9399 err = StartChildProcess(buf, "", &cps->pr);
9403 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9404 DisplayFatalError(buf, err, 1);
9410 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9411 if (cps->protocolVersion > 1) {
9412 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9413 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9414 cps->comboCnt = 0; // and values of combo boxes
9415 SendToProgram(buf, cps);
9417 SendToProgram("xboard\n", cps);
9422 TwoMachinesEventIfReady P((void))
9424 static int curMess = 0;
9425 if (first.lastPing != first.lastPong) {
9426 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9427 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9430 if (second.lastPing != second.lastPong) {
9431 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9432 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9435 DisplayMessage("", ""); curMess = 0;
9441 CreateTourney(char *name)
9444 if(name[0] == NULLCHAR) return 0;
9445 f = fopen(appData.tourneyFile, "r");
9446 if(f) { // file exists
9447 ParseArgsFromFile(f); // parse it
9449 f = fopen(appData.tourneyFile, "w");
9450 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9451 // create a file with tournament description
9452 fprintf(f, "-participants {%s}\n", appData.participants);
9453 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9454 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9455 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9456 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9457 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9458 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9459 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9460 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9461 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9462 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9463 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9464 fprintf(f, "-results \"\"\n");
9468 appData.noChessProgram = FALSE;
9469 appData.clockMode = TRUE;
9474 #define MAXENGINES 1000
9475 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9477 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9479 char buf[MSG_SIZ], *p, *q;
9483 while(*p && *p != '\n') *q++ = *p++;
9485 if(engineList[i]) free(engineList[i]);
9486 engineList[i] = strdup(buf);
9488 TidyProgramName(engineList[i], "localhost", buf);
9489 if(engineMnemonic[i]) free(engineMnemonic[i]);
9490 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9492 sscanf(q + 8, "%s", buf + strlen(buf));
9495 engineMnemonic[i] = strdup(buf);
9497 if(i > MAXENGINES - 2) break;
9499 engineList[i] = NULL;
9502 // following implemented as macro to avoid type limitations
9503 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9505 void SwapEngines(int n)
9506 { // swap settings for first engine and other engine (so far only some selected options)
9511 SWAP(chessProgram, p)
9513 SWAP(hasOwnBookUCI, h)
9514 SWAP(protocolVersion, h)
9516 SWAP(scoreIsAbsolute, h)
9522 SetPlayer(int player)
9523 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9525 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9526 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9527 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9528 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9529 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9530 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9532 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9533 ParseArgsFromString(resetOptions);
9534 ParseArgsFromString(buf);
9540 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9541 { // determine players from game number
9542 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9544 if(appData.tourneyType == 0) {
9545 roundsPerCycle = (nPlayers - 1) | 1;
9546 pairingsPerRound = nPlayers / 2;
9547 } else if(appData.tourneyType > 0) {
9548 roundsPerCycle = nPlayers - appData.tourneyType;
9549 pairingsPerRound = appData.tourneyType;
9551 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9552 gamesPerCycle = gamesPerRound * roundsPerCycle;
9553 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9554 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9555 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9556 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9557 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9558 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9560 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9561 if(appData.roundSync) *syncInterval = gamesPerRound;
9563 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9565 if(appData.tourneyType == 0) {
9566 if(curPairing == (nPlayers-1)/2 ) {
9567 *whitePlayer = curRound;
9568 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9570 *whitePlayer = curRound - pairingsPerRound + curPairing;
9571 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9572 *blackPlayer = curRound + pairingsPerRound - curPairing;
9573 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9575 } else if(appData.tourneyType > 0) {
9576 *whitePlayer = curPairing;
9577 *blackPlayer = curRound + appData.tourneyType;
9580 // take care of white/black alternation per round.
9581 // For cycles and games this is already taken care of by default, derived from matchGame!
9582 return curRound & 1;
9586 NextTourneyGame(int nr, int *swapColors)
9587 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9589 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9591 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9592 tf = fopen(appData.tourneyFile, "r");
9593 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9594 ParseArgsFromFile(tf); fclose(tf);
9596 p = appData.participants;
9597 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9598 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9601 p = q = appData.results;
9602 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9603 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9604 DisplayMessage(_("Waiting for other game(s)"),"");
9605 waitingForGame = TRUE;
9606 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9609 waitingForGame = FALSE;
9612 if(first.pr != NoProc) return 1; // engines already loaded
9614 // redefine engines, engine dir, etc.
9615 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9616 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9618 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9619 SwapEngines(1); // and make that valid for second engine by swapping
9620 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9621 InitEngine(&second, 1);
9622 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9628 { // performs game initialization that does not invoke engines, and then tries to start the game
9629 int firstWhite, swapColors = 0;
9630 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9631 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9632 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9633 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9634 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9635 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9636 Reset(FALSE, first.pr != NoProc);
9637 appData.noChessProgram = FALSE;
9638 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9642 void UserAdjudicationEvent( int result )
9644 ChessMove gameResult = GameIsDrawn;
9647 gameResult = WhiteWins;
9649 else if( result < 0 ) {
9650 gameResult = BlackWins;
9653 if( gameMode == TwoMachinesPlay ) {
9654 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9659 // [HGM] save: calculate checksum of game to make games easily identifiable
9660 int StringCheckSum(char *s)
9663 if(s==NULL) return 0;
9664 while(*s) i = i*259 + *s++;
9671 for(i=backwardMostMove; i<forwardMostMove; i++) {
9672 sum += pvInfoList[i].depth;
9673 sum += StringCheckSum(parseList[i]);
9674 sum += StringCheckSum(commentList[i]);
9677 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9678 return sum + StringCheckSum(commentList[i]);
9679 } // end of save patch
9682 GameEnds(result, resultDetails, whosays)
9684 char *resultDetails;
9687 GameMode nextGameMode;
9689 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9691 if(endingGame) return; /* [HGM] crash: forbid recursion */
9693 if(twoBoards) { // [HGM] dual: switch back to one board
9694 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9695 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9697 if (appData.debugMode) {
9698 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9699 result, resultDetails ? resultDetails : "(null)", whosays);
9702 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9704 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9705 /* If we are playing on ICS, the server decides when the
9706 game is over, but the engine can offer to draw, claim
9710 if (appData.zippyPlay && first.initDone) {
9711 if (result == GameIsDrawn) {
9712 /* In case draw still needs to be claimed */
9713 SendToICS(ics_prefix);
9714 SendToICS("draw\n");
9715 } else if (StrCaseStr(resultDetails, "resign")) {
9716 SendToICS(ics_prefix);
9717 SendToICS("resign\n");
9721 endingGame = 0; /* [HGM] crash */
9725 /* If we're loading the game from a file, stop */
9726 if (whosays == GE_FILE) {
9727 (void) StopLoadGameTimer();
9731 /* Cancel draw offers */
9732 first.offeredDraw = second.offeredDraw = 0;
9734 /* If this is an ICS game, only ICS can really say it's done;
9735 if not, anyone can. */
9736 isIcsGame = (gameMode == IcsPlayingWhite ||
9737 gameMode == IcsPlayingBlack ||
9738 gameMode == IcsObserving ||
9739 gameMode == IcsExamining);
9741 if (!isIcsGame || whosays == GE_ICS) {
9742 /* OK -- not an ICS game, or ICS said it was done */
9744 if (!isIcsGame && !appData.noChessProgram)
9745 SetUserThinkingEnables();
9747 /* [HGM] if a machine claims the game end we verify this claim */
9748 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9749 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9751 ChessMove trueResult = (ChessMove) -1;
9753 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9754 first.twoMachinesColor[0] :
9755 second.twoMachinesColor[0] ;
9757 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9758 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9759 /* [HGM] verify: engine mate claims accepted if they were flagged */
9760 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9762 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9763 /* [HGM] verify: engine mate claims accepted if they were flagged */
9764 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9766 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9767 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9770 // now verify win claims, but not in drop games, as we don't understand those yet
9771 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9772 || gameInfo.variant == VariantGreat) &&
9773 (result == WhiteWins && claimer == 'w' ||
9774 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9775 if (appData.debugMode) {
9776 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9777 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9779 if(result != trueResult) {
9780 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9781 result = claimer == 'w' ? BlackWins : WhiteWins;
9782 resultDetails = buf;
9785 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9786 && (forwardMostMove <= backwardMostMove ||
9787 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9788 (claimer=='b')==(forwardMostMove&1))
9790 /* [HGM] verify: draws that were not flagged are false claims */
9791 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9792 result = claimer == 'w' ? BlackWins : WhiteWins;
9793 resultDetails = buf;
9795 /* (Claiming a loss is accepted no questions asked!) */
9797 /* [HGM] bare: don't allow bare King to win */
9798 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9799 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9800 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9801 && result != GameIsDrawn)
9802 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9803 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9804 int p = (signed char)boards[forwardMostMove][i][j] - color;
9805 if(p >= 0 && p <= (int)WhiteKing) k++;
9807 if (appData.debugMode) {
9808 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9809 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9812 result = GameIsDrawn;
9813 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9814 resultDetails = buf;
9820 if(serverMoves != NULL && !loadFlag) { char c = '=';
9821 if(result==WhiteWins) c = '+';
9822 if(result==BlackWins) c = '-';
9823 if(resultDetails != NULL)
9824 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9826 if (resultDetails != NULL) {
9827 gameInfo.result = result;
9828 gameInfo.resultDetails = StrSave(resultDetails);
9830 /* display last move only if game was not loaded from file */
9831 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9832 DisplayMove(currentMove - 1);
9834 if (forwardMostMove != 0) {
9835 if (gameMode != PlayFromGameFile && gameMode != EditGame
9836 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9838 if (*appData.saveGameFile != NULLCHAR) {
9839 SaveGameToFile(appData.saveGameFile, TRUE);
9840 } else if (appData.autoSaveGames) {
9843 if (*appData.savePositionFile != NULLCHAR) {
9844 SavePositionToFile(appData.savePositionFile);
9849 /* Tell program how game ended in case it is learning */
9850 /* [HGM] Moved this to after saving the PGN, just in case */
9851 /* engine died and we got here through time loss. In that */
9852 /* case we will get a fatal error writing the pipe, which */
9853 /* would otherwise lose us the PGN. */
9854 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9855 /* output during GameEnds should never be fatal anymore */
9856 if (gameMode == MachinePlaysWhite ||
9857 gameMode == MachinePlaysBlack ||
9858 gameMode == TwoMachinesPlay ||
9859 gameMode == IcsPlayingWhite ||
9860 gameMode == IcsPlayingBlack ||
9861 gameMode == BeginningOfGame) {
9863 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9865 if (first.pr != NoProc) {
9866 SendToProgram(buf, &first);
9868 if (second.pr != NoProc &&
9869 gameMode == TwoMachinesPlay) {
9870 SendToProgram(buf, &second);
9875 if (appData.icsActive) {
9876 if (appData.quietPlay &&
9877 (gameMode == IcsPlayingWhite ||
9878 gameMode == IcsPlayingBlack)) {
9879 SendToICS(ics_prefix);
9880 SendToICS("set shout 1\n");
9882 nextGameMode = IcsIdle;
9883 ics_user_moved = FALSE;
9884 /* clean up premove. It's ugly when the game has ended and the
9885 * premove highlights are still on the board.
9889 ClearPremoveHighlights();
9890 DrawPosition(FALSE, boards[currentMove]);
9892 if (whosays == GE_ICS) {
9895 if (gameMode == IcsPlayingWhite)
9897 else if(gameMode == IcsPlayingBlack)
9901 if (gameMode == IcsPlayingBlack)
9903 else if(gameMode == IcsPlayingWhite)
9910 PlayIcsUnfinishedSound();
9913 } else if (gameMode == EditGame ||
9914 gameMode == PlayFromGameFile ||
9915 gameMode == AnalyzeMode ||
9916 gameMode == AnalyzeFile) {
9917 nextGameMode = gameMode;
9919 nextGameMode = EndOfGame;
9924 nextGameMode = gameMode;
9927 if (appData.noChessProgram) {
9928 gameMode = nextGameMode;
9930 endingGame = 0; /* [HGM] crash */
9935 /* Put first chess program into idle state */
9936 if (first.pr != NoProc &&
9937 (gameMode == MachinePlaysWhite ||
9938 gameMode == MachinePlaysBlack ||
9939 gameMode == TwoMachinesPlay ||
9940 gameMode == IcsPlayingWhite ||
9941 gameMode == IcsPlayingBlack ||
9942 gameMode == BeginningOfGame)) {
9943 SendToProgram("force\n", &first);
9944 if (first.usePing) {
9946 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9947 SendToProgram(buf, &first);
9950 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9951 /* Kill off first chess program */
9952 if (first.isr != NULL)
9953 RemoveInputSource(first.isr);
9956 if (first.pr != NoProc) {
9958 DoSleep( appData.delayBeforeQuit );
9959 SendToProgram("quit\n", &first);
9960 DoSleep( appData.delayAfterQuit );
9961 DestroyChildProcess(first.pr, first.useSigterm);
9966 /* Put second chess program into idle state */
9967 if (second.pr != NoProc &&
9968 gameMode == TwoMachinesPlay) {
9969 SendToProgram("force\n", &second);
9970 if (second.usePing) {
9972 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9973 SendToProgram(buf, &second);
9976 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9977 /* Kill off second chess program */
9978 if (second.isr != NULL)
9979 RemoveInputSource(second.isr);
9982 if (second.pr != NoProc) {
9983 DoSleep( appData.delayBeforeQuit );
9984 SendToProgram("quit\n", &second);
9985 DoSleep( appData.delayAfterQuit );
9986 DestroyChildProcess(second.pr, second.useSigterm);
9991 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9996 if (first.twoMachinesColor[0] == 'w') {
10004 if (first.twoMachinesColor[0] == 'b') {
10007 second.matchWins++;
10010 case GameUnfinished:
10016 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10017 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10018 ReserveGame(nextGame, resChar); // sets nextGame
10019 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10020 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10022 if (nextGame <= appData.matchGames) {
10023 gameMode = nextGameMode;
10024 matchGame = nextGame; // this will be overruled in tourney mode!
10025 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10026 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10027 endingGame = 0; /* [HGM] crash */
10030 gameMode = nextGameMode;
10031 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10032 first.tidy, second.tidy,
10033 first.matchWins, second.matchWins,
10034 appData.matchGames - (first.matchWins + second.matchWins));
10035 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10036 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10037 first.twoMachinesColor = "black\n";
10038 second.twoMachinesColor = "white\n";
10040 first.twoMachinesColor = "white\n";
10041 second.twoMachinesColor = "black\n";
10045 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10046 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10048 gameMode = nextGameMode;
10050 endingGame = 0; /* [HGM] crash */
10051 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10052 if(matchMode == TRUE) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10053 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10054 DisplayNote(ranking ? ranking : buf);
10056 if(ranking) free(ranking);
10060 /* Assumes program was just initialized (initString sent).
10061 Leaves program in force mode. */
10063 FeedMovesToProgram(cps, upto)
10064 ChessProgramState *cps;
10069 if (appData.debugMode)
10070 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10071 startedFromSetupPosition ? "position and " : "",
10072 backwardMostMove, upto, cps->which);
10073 if(currentlyInitializedVariant != gameInfo.variant) {
10075 // [HGM] variantswitch: make engine aware of new variant
10076 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10077 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10078 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10079 SendToProgram(buf, cps);
10080 currentlyInitializedVariant = gameInfo.variant;
10082 SendToProgram("force\n", cps);
10083 if (startedFromSetupPosition) {
10084 SendBoard(cps, backwardMostMove);
10085 if (appData.debugMode) {
10086 fprintf(debugFP, "feedMoves\n");
10089 for (i = backwardMostMove; i < upto; i++) {
10090 SendMoveToProgram(i, cps);
10096 ResurrectChessProgram()
10098 /* The chess program may have exited.
10099 If so, restart it and feed it all the moves made so far. */
10100 static int doInit = 0;
10102 if (appData.noChessProgram) return 1;
10104 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10105 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10106 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10107 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10109 if (first.pr != NoProc) return 1;
10110 StartChessProgram(&first);
10112 InitChessProgram(&first, FALSE);
10113 FeedMovesToProgram(&first, currentMove);
10115 if (!first.sendTime) {
10116 /* can't tell gnuchess what its clock should read,
10117 so we bow to its notion. */
10119 timeRemaining[0][currentMove] = whiteTimeRemaining;
10120 timeRemaining[1][currentMove] = blackTimeRemaining;
10123 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10124 appData.icsEngineAnalyze) && first.analysisSupport) {
10125 SendToProgram("analyze\n", &first);
10126 first.analyzing = TRUE;
10132 * Button procedures
10135 Reset(redraw, init)
10140 if (appData.debugMode) {
10141 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10142 redraw, init, gameMode);
10144 CleanupTail(); // [HGM] vari: delete any stored variations
10145 pausing = pauseExamInvalid = FALSE;
10146 startedFromSetupPosition = blackPlaysFirst = FALSE;
10148 whiteFlag = blackFlag = FALSE;
10149 userOfferedDraw = FALSE;
10150 hintRequested = bookRequested = FALSE;
10151 first.maybeThinking = FALSE;
10152 second.maybeThinking = FALSE;
10153 first.bookSuspend = FALSE; // [HGM] book
10154 second.bookSuspend = FALSE;
10155 thinkOutput[0] = NULLCHAR;
10156 lastHint[0] = NULLCHAR;
10157 ClearGameInfo(&gameInfo);
10158 gameInfo.variant = StringToVariant(appData.variant);
10159 ics_user_moved = ics_clock_paused = FALSE;
10160 ics_getting_history = H_FALSE;
10162 white_holding[0] = black_holding[0] = NULLCHAR;
10163 ClearProgramStats();
10164 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10168 flipView = appData.flipView;
10169 ClearPremoveHighlights();
10170 gotPremove = FALSE;
10171 alarmSounded = FALSE;
10173 GameEnds(EndOfFile, NULL, GE_PLAYER);
10174 if(appData.serverMovesName != NULL) {
10175 /* [HGM] prepare to make moves file for broadcasting */
10176 clock_t t = clock();
10177 if(serverMoves != NULL) fclose(serverMoves);
10178 serverMoves = fopen(appData.serverMovesName, "r");
10179 if(serverMoves != NULL) {
10180 fclose(serverMoves);
10181 /* delay 15 sec before overwriting, so all clients can see end */
10182 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10184 serverMoves = fopen(appData.serverMovesName, "w");
10188 gameMode = BeginningOfGame;
10190 if(appData.icsActive) gameInfo.variant = VariantNormal;
10191 currentMove = forwardMostMove = backwardMostMove = 0;
10192 InitPosition(redraw);
10193 for (i = 0; i < MAX_MOVES; i++) {
10194 if (commentList[i] != NULL) {
10195 free(commentList[i]);
10196 commentList[i] = NULL;
10200 timeRemaining[0][0] = whiteTimeRemaining;
10201 timeRemaining[1][0] = blackTimeRemaining;
10203 if (first.pr == NULL) {
10204 StartChessProgram(&first);
10207 InitChessProgram(&first, startedFromSetupPosition);
10210 DisplayMessage("", "");
10211 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10212 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10219 if (!AutoPlayOneMove())
10221 if (matchMode || appData.timeDelay == 0)
10223 if (appData.timeDelay < 0)
10225 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10234 int fromX, fromY, toX, toY;
10236 if (appData.debugMode) {
10237 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10240 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10243 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10244 pvInfoList[currentMove].depth = programStats.depth;
10245 pvInfoList[currentMove].score = programStats.score;
10246 pvInfoList[currentMove].time = 0;
10247 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10250 if (currentMove >= forwardMostMove) {
10251 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10252 gameMode = EditGame;
10255 /* [AS] Clear current move marker at the end of a game */
10256 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10261 toX = moveList[currentMove][2] - AAA;
10262 toY = moveList[currentMove][3] - ONE;
10264 if (moveList[currentMove][1] == '@') {
10265 if (appData.highlightLastMove) {
10266 SetHighlights(-1, -1, toX, toY);
10269 fromX = moveList[currentMove][0] - AAA;
10270 fromY = moveList[currentMove][1] - ONE;
10272 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10274 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10276 if (appData.highlightLastMove) {
10277 SetHighlights(fromX, fromY, toX, toY);
10280 DisplayMove(currentMove);
10281 SendMoveToProgram(currentMove++, &first);
10282 DisplayBothClocks();
10283 DrawPosition(FALSE, boards[currentMove]);
10284 // [HGM] PV info: always display, routine tests if empty
10285 DisplayComment(currentMove - 1, commentList[currentMove]);
10291 LoadGameOneMove(readAhead)
10292 ChessMove readAhead;
10294 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10295 char promoChar = NULLCHAR;
10296 ChessMove moveType;
10297 char move[MSG_SIZ];
10300 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10301 gameMode != AnalyzeMode && gameMode != Training) {
10306 yyboardindex = forwardMostMove;
10307 if (readAhead != EndOfFile) {
10308 moveType = readAhead;
10310 if (gameFileFP == NULL)
10312 moveType = (ChessMove) Myylex();
10316 switch (moveType) {
10318 if (appData.debugMode)
10319 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10322 /* append the comment but don't display it */
10323 AppendComment(currentMove, p, FALSE);
10326 case WhiteCapturesEnPassant:
10327 case BlackCapturesEnPassant:
10328 case WhitePromotion:
10329 case BlackPromotion:
10330 case WhiteNonPromotion:
10331 case BlackNonPromotion:
10333 case WhiteKingSideCastle:
10334 case WhiteQueenSideCastle:
10335 case BlackKingSideCastle:
10336 case BlackQueenSideCastle:
10337 case WhiteKingSideCastleWild:
10338 case WhiteQueenSideCastleWild:
10339 case BlackKingSideCastleWild:
10340 case BlackQueenSideCastleWild:
10342 case WhiteHSideCastleFR:
10343 case WhiteASideCastleFR:
10344 case BlackHSideCastleFR:
10345 case BlackASideCastleFR:
10347 if (appData.debugMode)
10348 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10349 fromX = currentMoveString[0] - AAA;
10350 fromY = currentMoveString[1] - ONE;
10351 toX = currentMoveString[2] - AAA;
10352 toY = currentMoveString[3] - ONE;
10353 promoChar = currentMoveString[4];
10358 if (appData.debugMode)
10359 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10360 fromX = moveType == WhiteDrop ?
10361 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10362 (int) CharToPiece(ToLower(currentMoveString[0]));
10364 toX = currentMoveString[2] - AAA;
10365 toY = currentMoveString[3] - ONE;
10371 case GameUnfinished:
10372 if (appData.debugMode)
10373 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10374 p = strchr(yy_text, '{');
10375 if (p == NULL) p = strchr(yy_text, '(');
10378 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10380 q = strchr(p, *p == '{' ? '}' : ')');
10381 if (q != NULL) *q = NULLCHAR;
10384 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10385 GameEnds(moveType, p, GE_FILE);
10387 if (cmailMsgLoaded) {
10389 flipView = WhiteOnMove(currentMove);
10390 if (moveType == GameUnfinished) flipView = !flipView;
10391 if (appData.debugMode)
10392 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10397 if (appData.debugMode)
10398 fprintf(debugFP, "Parser hit end of file\n");
10399 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10405 if (WhiteOnMove(currentMove)) {
10406 GameEnds(BlackWins, "Black mates", GE_FILE);
10408 GameEnds(WhiteWins, "White mates", GE_FILE);
10412 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10418 case MoveNumberOne:
10419 if (lastLoadGameStart == GNUChessGame) {
10420 /* GNUChessGames have numbers, but they aren't move numbers */
10421 if (appData.debugMode)
10422 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10423 yy_text, (int) moveType);
10424 return LoadGameOneMove(EndOfFile); /* tail recursion */
10426 /* else fall thru */
10431 /* Reached start of next game in file */
10432 if (appData.debugMode)
10433 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10434 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10440 if (WhiteOnMove(currentMove)) {
10441 GameEnds(BlackWins, "Black mates", GE_FILE);
10443 GameEnds(WhiteWins, "White mates", GE_FILE);
10447 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10453 case PositionDiagram: /* should not happen; ignore */
10454 case ElapsedTime: /* ignore */
10455 case NAG: /* ignore */
10456 if (appData.debugMode)
10457 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10458 yy_text, (int) moveType);
10459 return LoadGameOneMove(EndOfFile); /* tail recursion */
10462 if (appData.testLegality) {
10463 if (appData.debugMode)
10464 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10465 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10466 (forwardMostMove / 2) + 1,
10467 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10468 DisplayError(move, 0);
10471 if (appData.debugMode)
10472 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10473 yy_text, currentMoveString);
10474 fromX = currentMoveString[0] - AAA;
10475 fromY = currentMoveString[1] - ONE;
10476 toX = currentMoveString[2] - AAA;
10477 toY = currentMoveString[3] - ONE;
10478 promoChar = currentMoveString[4];
10482 case AmbiguousMove:
10483 if (appData.debugMode)
10484 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10485 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10486 (forwardMostMove / 2) + 1,
10487 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10488 DisplayError(move, 0);
10493 case ImpossibleMove:
10494 if (appData.debugMode)
10495 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10496 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10497 (forwardMostMove / 2) + 1,
10498 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10499 DisplayError(move, 0);
10505 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10506 DrawPosition(FALSE, boards[currentMove]);
10507 DisplayBothClocks();
10508 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10509 DisplayComment(currentMove - 1, commentList[currentMove]);
10511 (void) StopLoadGameTimer();
10513 cmailOldMove = forwardMostMove;
10516 /* currentMoveString is set as a side-effect of yylex */
10518 thinkOutput[0] = NULLCHAR;
10519 MakeMove(fromX, fromY, toX, toY, promoChar);
10520 currentMove = forwardMostMove;
10525 /* Load the nth game from the given file */
10527 LoadGameFromFile(filename, n, title, useList)
10531 /*Boolean*/ int useList;
10536 if (strcmp(filename, "-") == 0) {
10540 f = fopen(filename, "rb");
10542 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10543 DisplayError(buf, errno);
10547 if (fseek(f, 0, 0) == -1) {
10548 /* f is not seekable; probably a pipe */
10551 if (useList && n == 0) {
10552 int error = GameListBuild(f);
10554 DisplayError(_("Cannot build game list"), error);
10555 } else if (!ListEmpty(&gameList) &&
10556 ((ListGame *) gameList.tailPred)->number > 1) {
10557 GameListPopUp(f, title);
10564 return LoadGame(f, n, title, FALSE);
10569 MakeRegisteredMove()
10571 int fromX, fromY, toX, toY;
10573 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10574 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10577 if (appData.debugMode)
10578 fprintf(debugFP, "Restoring %s for game %d\n",
10579 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10581 thinkOutput[0] = NULLCHAR;
10582 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10583 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10584 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10585 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10586 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10587 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10588 MakeMove(fromX, fromY, toX, toY, promoChar);
10589 ShowMove(fromX, fromY, toX, toY);
10591 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10598 if (WhiteOnMove(currentMove)) {
10599 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10601 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10606 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10613 if (WhiteOnMove(currentMove)) {
10614 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10616 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10621 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10632 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10634 CmailLoadGame(f, gameNumber, title, useList)
10642 if (gameNumber > nCmailGames) {
10643 DisplayError(_("No more games in this message"), 0);
10646 if (f == lastLoadGameFP) {
10647 int offset = gameNumber - lastLoadGameNumber;
10649 cmailMsg[0] = NULLCHAR;
10650 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10651 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10652 nCmailMovesRegistered--;
10654 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10655 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10656 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10659 if (! RegisterMove()) return FALSE;
10663 retVal = LoadGame(f, gameNumber, title, useList);
10665 /* Make move registered during previous look at this game, if any */
10666 MakeRegisteredMove();
10668 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10669 commentList[currentMove]
10670 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10671 DisplayComment(currentMove - 1, commentList[currentMove]);
10677 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10682 int gameNumber = lastLoadGameNumber + offset;
10683 if (lastLoadGameFP == NULL) {
10684 DisplayError(_("No game has been loaded yet"), 0);
10687 if (gameNumber <= 0) {
10688 DisplayError(_("Can't back up any further"), 0);
10691 if (cmailMsgLoaded) {
10692 return CmailLoadGame(lastLoadGameFP, gameNumber,
10693 lastLoadGameTitle, lastLoadGameUseList);
10695 return LoadGame(lastLoadGameFP, gameNumber,
10696 lastLoadGameTitle, lastLoadGameUseList);
10702 /* Load the nth game from open file f */
10704 LoadGame(f, gameNumber, title, useList)
10712 int gn = gameNumber;
10713 ListGame *lg = NULL;
10714 int numPGNTags = 0;
10716 GameMode oldGameMode;
10717 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10719 if (appData.debugMode)
10720 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10722 if (gameMode == Training )
10723 SetTrainingModeOff();
10725 oldGameMode = gameMode;
10726 if (gameMode != BeginningOfGame) {
10727 Reset(FALSE, TRUE);
10731 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10732 fclose(lastLoadGameFP);
10736 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10739 fseek(f, lg->offset, 0);
10740 GameListHighlight(gameNumber);
10744 DisplayError(_("Game number out of range"), 0);
10749 if (fseek(f, 0, 0) == -1) {
10750 if (f == lastLoadGameFP ?
10751 gameNumber == lastLoadGameNumber + 1 :
10755 DisplayError(_("Can't seek on game file"), 0);
10760 lastLoadGameFP = f;
10761 lastLoadGameNumber = gameNumber;
10762 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10763 lastLoadGameUseList = useList;
10767 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10768 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10769 lg->gameInfo.black);
10771 } else if (*title != NULLCHAR) {
10772 if (gameNumber > 1) {
10773 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10776 DisplayTitle(title);
10780 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10781 gameMode = PlayFromGameFile;
10785 currentMove = forwardMostMove = backwardMostMove = 0;
10786 CopyBoard(boards[0], initialPosition);
10790 * Skip the first gn-1 games in the file.
10791 * Also skip over anything that precedes an identifiable
10792 * start of game marker, to avoid being confused by
10793 * garbage at the start of the file. Currently
10794 * recognized start of game markers are the move number "1",
10795 * the pattern "gnuchess .* game", the pattern
10796 * "^[#;%] [^ ]* game file", and a PGN tag block.
10797 * A game that starts with one of the latter two patterns
10798 * will also have a move number 1, possibly
10799 * following a position diagram.
10800 * 5-4-02: Let's try being more lenient and allowing a game to
10801 * start with an unnumbered move. Does that break anything?
10803 cm = lastLoadGameStart = EndOfFile;
10805 yyboardindex = forwardMostMove;
10806 cm = (ChessMove) Myylex();
10809 if (cmailMsgLoaded) {
10810 nCmailGames = CMAIL_MAX_GAMES - gn;
10813 DisplayError(_("Game not found in file"), 0);
10820 lastLoadGameStart = cm;
10823 case MoveNumberOne:
10824 switch (lastLoadGameStart) {
10829 case MoveNumberOne:
10831 gn--; /* count this game */
10832 lastLoadGameStart = cm;
10841 switch (lastLoadGameStart) {
10844 case MoveNumberOne:
10846 gn--; /* count this game */
10847 lastLoadGameStart = cm;
10850 lastLoadGameStart = cm; /* game counted already */
10858 yyboardindex = forwardMostMove;
10859 cm = (ChessMove) Myylex();
10860 } while (cm == PGNTag || cm == Comment);
10867 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10868 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10869 != CMAIL_OLD_RESULT) {
10871 cmailResult[ CMAIL_MAX_GAMES
10872 - gn - 1] = CMAIL_OLD_RESULT;
10878 /* Only a NormalMove can be at the start of a game
10879 * without a position diagram. */
10880 if (lastLoadGameStart == EndOfFile ) {
10882 lastLoadGameStart = MoveNumberOne;
10891 if (appData.debugMode)
10892 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10894 if (cm == XBoardGame) {
10895 /* Skip any header junk before position diagram and/or move 1 */
10897 yyboardindex = forwardMostMove;
10898 cm = (ChessMove) Myylex();
10900 if (cm == EndOfFile ||
10901 cm == GNUChessGame || cm == XBoardGame) {
10902 /* Empty game; pretend end-of-file and handle later */
10907 if (cm == MoveNumberOne || cm == PositionDiagram ||
10908 cm == PGNTag || cm == Comment)
10911 } else if (cm == GNUChessGame) {
10912 if (gameInfo.event != NULL) {
10913 free(gameInfo.event);
10915 gameInfo.event = StrSave(yy_text);
10918 startedFromSetupPosition = FALSE;
10919 while (cm == PGNTag) {
10920 if (appData.debugMode)
10921 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10922 err = ParsePGNTag(yy_text, &gameInfo);
10923 if (!err) numPGNTags++;
10925 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10926 if(gameInfo.variant != oldVariant) {
10927 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10928 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10929 InitPosition(TRUE);
10930 oldVariant = gameInfo.variant;
10931 if (appData.debugMode)
10932 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10936 if (gameInfo.fen != NULL) {
10937 Board initial_position;
10938 startedFromSetupPosition = TRUE;
10939 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10941 DisplayError(_("Bad FEN position in file"), 0);
10944 CopyBoard(boards[0], initial_position);
10945 if (blackPlaysFirst) {
10946 currentMove = forwardMostMove = backwardMostMove = 1;
10947 CopyBoard(boards[1], initial_position);
10948 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10949 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10950 timeRemaining[0][1] = whiteTimeRemaining;
10951 timeRemaining[1][1] = blackTimeRemaining;
10952 if (commentList[0] != NULL) {
10953 commentList[1] = commentList[0];
10954 commentList[0] = NULL;
10957 currentMove = forwardMostMove = backwardMostMove = 0;
10959 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10961 initialRulePlies = FENrulePlies;
10962 for( i=0; i< nrCastlingRights; i++ )
10963 initialRights[i] = initial_position[CASTLING][i];
10965 yyboardindex = forwardMostMove;
10966 free(gameInfo.fen);
10967 gameInfo.fen = NULL;
10970 yyboardindex = forwardMostMove;
10971 cm = (ChessMove) Myylex();
10973 /* Handle comments interspersed among the tags */
10974 while (cm == Comment) {
10976 if (appData.debugMode)
10977 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10979 AppendComment(currentMove, p, FALSE);
10980 yyboardindex = forwardMostMove;
10981 cm = (ChessMove) Myylex();
10985 /* don't rely on existence of Event tag since if game was
10986 * pasted from clipboard the Event tag may not exist
10988 if (numPGNTags > 0){
10990 if (gameInfo.variant == VariantNormal) {
10991 VariantClass v = StringToVariant(gameInfo.event);
10992 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10993 if(v < VariantShogi) gameInfo.variant = v;
10996 if( appData.autoDisplayTags ) {
10997 tags = PGNTags(&gameInfo);
10998 TagsPopUp(tags, CmailMsg());
11003 /* Make something up, but don't display it now */
11008 if (cm == PositionDiagram) {
11011 Board initial_position;
11013 if (appData.debugMode)
11014 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11016 if (!startedFromSetupPosition) {
11018 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11019 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11030 initial_position[i][j++] = CharToPiece(*p);
11033 while (*p == ' ' || *p == '\t' ||
11034 *p == '\n' || *p == '\r') p++;
11036 if (strncmp(p, "black", strlen("black"))==0)
11037 blackPlaysFirst = TRUE;
11039 blackPlaysFirst = FALSE;
11040 startedFromSetupPosition = TRUE;
11042 CopyBoard(boards[0], initial_position);
11043 if (blackPlaysFirst) {
11044 currentMove = forwardMostMove = backwardMostMove = 1;
11045 CopyBoard(boards[1], initial_position);
11046 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11047 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11048 timeRemaining[0][1] = whiteTimeRemaining;
11049 timeRemaining[1][1] = blackTimeRemaining;
11050 if (commentList[0] != NULL) {
11051 commentList[1] = commentList[0];
11052 commentList[0] = NULL;
11055 currentMove = forwardMostMove = backwardMostMove = 0;
11058 yyboardindex = forwardMostMove;
11059 cm = (ChessMove) Myylex();
11062 if (first.pr == NoProc) {
11063 StartChessProgram(&first);
11065 InitChessProgram(&first, FALSE);
11066 SendToProgram("force\n", &first);
11067 if (startedFromSetupPosition) {
11068 SendBoard(&first, forwardMostMove);
11069 if (appData.debugMode) {
11070 fprintf(debugFP, "Load Game\n");
11072 DisplayBothClocks();
11075 /* [HGM] server: flag to write setup moves in broadcast file as one */
11076 loadFlag = appData.suppressLoadMoves;
11078 while (cm == Comment) {
11080 if (appData.debugMode)
11081 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11083 AppendComment(currentMove, p, FALSE);
11084 yyboardindex = forwardMostMove;
11085 cm = (ChessMove) Myylex();
11088 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11089 cm == WhiteWins || cm == BlackWins ||
11090 cm == GameIsDrawn || cm == GameUnfinished) {
11091 DisplayMessage("", _("No moves in game"));
11092 if (cmailMsgLoaded) {
11093 if (appData.debugMode)
11094 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11098 DrawPosition(FALSE, boards[currentMove]);
11099 DisplayBothClocks();
11100 gameMode = EditGame;
11107 // [HGM] PV info: routine tests if comment empty
11108 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11109 DisplayComment(currentMove - 1, commentList[currentMove]);
11111 if (!matchMode && appData.timeDelay != 0)
11112 DrawPosition(FALSE, boards[currentMove]);
11114 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11115 programStats.ok_to_send = 1;
11118 /* if the first token after the PGN tags is a move
11119 * and not move number 1, retrieve it from the parser
11121 if (cm != MoveNumberOne)
11122 LoadGameOneMove(cm);
11124 /* load the remaining moves from the file */
11125 while (LoadGameOneMove(EndOfFile)) {
11126 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11127 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11130 /* rewind to the start of the game */
11131 currentMove = backwardMostMove;
11133 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11135 if (oldGameMode == AnalyzeFile ||
11136 oldGameMode == AnalyzeMode) {
11137 AnalyzeFileEvent();
11140 if (matchMode || appData.timeDelay == 0) {
11142 gameMode = EditGame;
11144 } else if (appData.timeDelay > 0) {
11145 AutoPlayGameLoop();
11148 if (appData.debugMode)
11149 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11151 loadFlag = 0; /* [HGM] true game starts */
11155 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11157 ReloadPosition(offset)
11160 int positionNumber = lastLoadPositionNumber + offset;
11161 if (lastLoadPositionFP == NULL) {
11162 DisplayError(_("No position has been loaded yet"), 0);
11165 if (positionNumber <= 0) {
11166 DisplayError(_("Can't back up any further"), 0);
11169 return LoadPosition(lastLoadPositionFP, positionNumber,
11170 lastLoadPositionTitle);
11173 /* Load the nth position from the given file */
11175 LoadPositionFromFile(filename, n, title)
11183 if (strcmp(filename, "-") == 0) {
11184 return LoadPosition(stdin, n, "stdin");
11186 f = fopen(filename, "rb");
11188 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11189 DisplayError(buf, errno);
11192 return LoadPosition(f, n, title);
11197 /* Load the nth position from the given open file, and close it */
11199 LoadPosition(f, positionNumber, title)
11201 int positionNumber;
11204 char *p, line[MSG_SIZ];
11205 Board initial_position;
11206 int i, j, fenMode, pn;
11208 if (gameMode == Training )
11209 SetTrainingModeOff();
11211 if (gameMode != BeginningOfGame) {
11212 Reset(FALSE, TRUE);
11214 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11215 fclose(lastLoadPositionFP);
11217 if (positionNumber == 0) positionNumber = 1;
11218 lastLoadPositionFP = f;
11219 lastLoadPositionNumber = positionNumber;
11220 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11221 if (first.pr == NoProc) {
11222 StartChessProgram(&first);
11223 InitChessProgram(&first, FALSE);
11225 pn = positionNumber;
11226 if (positionNumber < 0) {
11227 /* Negative position number means to seek to that byte offset */
11228 if (fseek(f, -positionNumber, 0) == -1) {
11229 DisplayError(_("Can't seek on position file"), 0);
11234 if (fseek(f, 0, 0) == -1) {
11235 if (f == lastLoadPositionFP ?
11236 positionNumber == lastLoadPositionNumber + 1 :
11237 positionNumber == 1) {
11240 DisplayError(_("Can't seek on position file"), 0);
11245 /* See if this file is FEN or old-style xboard */
11246 if (fgets(line, MSG_SIZ, f) == NULL) {
11247 DisplayError(_("Position not found in file"), 0);
11250 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11251 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11254 if (fenMode || line[0] == '#') pn--;
11256 /* skip positions before number pn */
11257 if (fgets(line, MSG_SIZ, f) == NULL) {
11259 DisplayError(_("Position not found in file"), 0);
11262 if (fenMode || line[0] == '#') pn--;
11267 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11268 DisplayError(_("Bad FEN position in file"), 0);
11272 (void) fgets(line, MSG_SIZ, f);
11273 (void) fgets(line, MSG_SIZ, f);
11275 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11276 (void) fgets(line, MSG_SIZ, f);
11277 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11280 initial_position[i][j++] = CharToPiece(*p);
11284 blackPlaysFirst = FALSE;
11286 (void) fgets(line, MSG_SIZ, f);
11287 if (strncmp(line, "black", strlen("black"))==0)
11288 blackPlaysFirst = TRUE;
11291 startedFromSetupPosition = TRUE;
11293 SendToProgram("force\n", &first);
11294 CopyBoard(boards[0], initial_position);
11295 if (blackPlaysFirst) {
11296 currentMove = forwardMostMove = backwardMostMove = 1;
11297 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11298 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11299 CopyBoard(boards[1], initial_position);
11300 DisplayMessage("", _("Black to play"));
11302 currentMove = forwardMostMove = backwardMostMove = 0;
11303 DisplayMessage("", _("White to play"));
11305 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11306 SendBoard(&first, forwardMostMove);
11307 if (appData.debugMode) {
11309 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11310 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11311 fprintf(debugFP, "Load Position\n");
11314 if (positionNumber > 1) {
11315 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11316 DisplayTitle(line);
11318 DisplayTitle(title);
11320 gameMode = EditGame;
11323 timeRemaining[0][1] = whiteTimeRemaining;
11324 timeRemaining[1][1] = blackTimeRemaining;
11325 DrawPosition(FALSE, boards[currentMove]);
11332 CopyPlayerNameIntoFileName(dest, src)
11335 while (*src != NULLCHAR && *src != ',') {
11340 *(*dest)++ = *src++;
11345 char *DefaultFileName(ext)
11348 static char def[MSG_SIZ];
11351 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11353 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11355 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11357 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11364 /* Save the current game to the given file */
11366 SaveGameToFile(filename, append)
11374 if (strcmp(filename, "-") == 0) {
11375 return SaveGame(stdout, 0, NULL);
11377 f = fopen(filename, append ? "a" : "w");
11379 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11380 DisplayError(buf, errno);
11383 safeStrCpy(buf, lastMsg, MSG_SIZ);
11384 DisplayMessage(_("Waiting for access to save file"), "");
11385 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11386 DisplayMessage(_("Saving game"), "");
11387 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11388 result = SaveGame(f, 0, NULL);
11389 DisplayMessage(buf, "");
11399 static char buf[MSG_SIZ];
11402 p = strchr(str, ' ');
11403 if (p == NULL) return str;
11404 strncpy(buf, str, p - str);
11405 buf[p - str] = NULLCHAR;
11409 #define PGN_MAX_LINE 75
11411 #define PGN_SIDE_WHITE 0
11412 #define PGN_SIDE_BLACK 1
11415 static int FindFirstMoveOutOfBook( int side )
11419 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11420 int index = backwardMostMove;
11421 int has_book_hit = 0;
11423 if( (index % 2) != side ) {
11427 while( index < forwardMostMove ) {
11428 /* Check to see if engine is in book */
11429 int depth = pvInfoList[index].depth;
11430 int score = pvInfoList[index].score;
11436 else if( score == 0 && depth == 63 ) {
11437 in_book = 1; /* Zappa */
11439 else if( score == 2 && depth == 99 ) {
11440 in_book = 1; /* Abrok */
11443 has_book_hit += in_book;
11459 void GetOutOfBookInfo( char * buf )
11463 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11465 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11466 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11470 if( oob[0] >= 0 || oob[1] >= 0 ) {
11471 for( i=0; i<2; i++ ) {
11475 if( i > 0 && oob[0] >= 0 ) {
11476 strcat( buf, " " );
11479 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11480 sprintf( buf+strlen(buf), "%s%.2f",
11481 pvInfoList[idx].score >= 0 ? "+" : "",
11482 pvInfoList[idx].score / 100.0 );
11488 /* Save game in PGN style and close the file */
11493 int i, offset, linelen, newblock;
11497 int movelen, numlen, blank;
11498 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11500 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11502 tm = time((time_t *) NULL);
11504 PrintPGNTags(f, &gameInfo);
11506 if (backwardMostMove > 0 || startedFromSetupPosition) {
11507 char *fen = PositionToFEN(backwardMostMove, NULL);
11508 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11509 fprintf(f, "\n{--------------\n");
11510 PrintPosition(f, backwardMostMove);
11511 fprintf(f, "--------------}\n");
11515 /* [AS] Out of book annotation */
11516 if( appData.saveOutOfBookInfo ) {
11519 GetOutOfBookInfo( buf );
11521 if( buf[0] != '\0' ) {
11522 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11529 i = backwardMostMove;
11533 while (i < forwardMostMove) {
11534 /* Print comments preceding this move */
11535 if (commentList[i] != NULL) {
11536 if (linelen > 0) fprintf(f, "\n");
11537 fprintf(f, "%s", commentList[i]);
11542 /* Format move number */
11544 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11547 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11549 numtext[0] = NULLCHAR;
11551 numlen = strlen(numtext);
11554 /* Print move number */
11555 blank = linelen > 0 && numlen > 0;
11556 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11565 fprintf(f, "%s", numtext);
11569 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11570 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11573 blank = linelen > 0 && movelen > 0;
11574 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11583 fprintf(f, "%s", move_buffer);
11584 linelen += movelen;
11586 /* [AS] Add PV info if present */
11587 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11588 /* [HGM] add time */
11589 char buf[MSG_SIZ]; int seconds;
11591 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11597 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11600 seconds = (seconds + 4)/10; // round to full seconds
11602 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11604 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11607 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11608 pvInfoList[i].score >= 0 ? "+" : "",
11609 pvInfoList[i].score / 100.0,
11610 pvInfoList[i].depth,
11613 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11615 /* Print score/depth */
11616 blank = linelen > 0 && movelen > 0;
11617 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11626 fprintf(f, "%s", move_buffer);
11627 linelen += movelen;
11633 /* Start a new line */
11634 if (linelen > 0) fprintf(f, "\n");
11636 /* Print comments after last move */
11637 if (commentList[i] != NULL) {
11638 fprintf(f, "%s\n", commentList[i]);
11642 if (gameInfo.resultDetails != NULL &&
11643 gameInfo.resultDetails[0] != NULLCHAR) {
11644 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11645 PGNResult(gameInfo.result));
11647 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11651 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11655 /* Save game in old style and close the file */
11657 SaveGameOldStyle(f)
11663 tm = time((time_t *) NULL);
11665 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11668 if (backwardMostMove > 0 || startedFromSetupPosition) {
11669 fprintf(f, "\n[--------------\n");
11670 PrintPosition(f, backwardMostMove);
11671 fprintf(f, "--------------]\n");
11676 i = backwardMostMove;
11677 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11679 while (i < forwardMostMove) {
11680 if (commentList[i] != NULL) {
11681 fprintf(f, "[%s]\n", commentList[i]);
11684 if ((i % 2) == 1) {
11685 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11688 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11690 if (commentList[i] != NULL) {
11694 if (i >= forwardMostMove) {
11698 fprintf(f, "%s\n", parseList[i]);
11703 if (commentList[i] != NULL) {
11704 fprintf(f, "[%s]\n", commentList[i]);
11707 /* This isn't really the old style, but it's close enough */
11708 if (gameInfo.resultDetails != NULL &&
11709 gameInfo.resultDetails[0] != NULLCHAR) {
11710 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11711 gameInfo.resultDetails);
11713 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11720 /* Save the current game to open file f and close the file */
11722 SaveGame(f, dummy, dummy2)
11727 if (gameMode == EditPosition) EditPositionDone(TRUE);
11728 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11729 if (appData.oldSaveStyle)
11730 return SaveGameOldStyle(f);
11732 return SaveGamePGN(f);
11735 /* Save the current position to the given file */
11737 SavePositionToFile(filename)
11743 if (strcmp(filename, "-") == 0) {
11744 return SavePosition(stdout, 0, NULL);
11746 f = fopen(filename, "a");
11748 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11749 DisplayError(buf, errno);
11752 safeStrCpy(buf, lastMsg, MSG_SIZ);
11753 DisplayMessage(_("Waiting for access to save file"), "");
11754 flock(fileno(f), LOCK_EX); // [HGM] lock
11755 DisplayMessage(_("Saving position"), "");
11756 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11757 SavePosition(f, 0, NULL);
11758 DisplayMessage(buf, "");
11764 /* Save the current position to the given open file and close the file */
11766 SavePosition(f, dummy, dummy2)
11774 if (gameMode == EditPosition) EditPositionDone(TRUE);
11775 if (appData.oldSaveStyle) {
11776 tm = time((time_t *) NULL);
11778 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11780 fprintf(f, "[--------------\n");
11781 PrintPosition(f, currentMove);
11782 fprintf(f, "--------------]\n");
11784 fen = PositionToFEN(currentMove, NULL);
11785 fprintf(f, "%s\n", fen);
11793 ReloadCmailMsgEvent(unregister)
11797 static char *inFilename = NULL;
11798 static char *outFilename;
11800 struct stat inbuf, outbuf;
11803 /* Any registered moves are unregistered if unregister is set, */
11804 /* i.e. invoked by the signal handler */
11806 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11807 cmailMoveRegistered[i] = FALSE;
11808 if (cmailCommentList[i] != NULL) {
11809 free(cmailCommentList[i]);
11810 cmailCommentList[i] = NULL;
11813 nCmailMovesRegistered = 0;
11816 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11817 cmailResult[i] = CMAIL_NOT_RESULT;
11821 if (inFilename == NULL) {
11822 /* Because the filenames are static they only get malloced once */
11823 /* and they never get freed */
11824 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11825 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11827 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11828 sprintf(outFilename, "%s.out", appData.cmailGameName);
11831 status = stat(outFilename, &outbuf);
11833 cmailMailedMove = FALSE;
11835 status = stat(inFilename, &inbuf);
11836 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11839 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11840 counts the games, notes how each one terminated, etc.
11842 It would be nice to remove this kludge and instead gather all
11843 the information while building the game list. (And to keep it
11844 in the game list nodes instead of having a bunch of fixed-size
11845 parallel arrays.) Note this will require getting each game's
11846 termination from the PGN tags, as the game list builder does
11847 not process the game moves. --mann
11849 cmailMsgLoaded = TRUE;
11850 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11852 /* Load first game in the file or popup game menu */
11853 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11855 #endif /* !WIN32 */
11863 char string[MSG_SIZ];
11865 if ( cmailMailedMove
11866 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11867 return TRUE; /* Allow free viewing */
11870 /* Unregister move to ensure that we don't leave RegisterMove */
11871 /* with the move registered when the conditions for registering no */
11873 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11874 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11875 nCmailMovesRegistered --;
11877 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11879 free(cmailCommentList[lastLoadGameNumber - 1]);
11880 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11884 if (cmailOldMove == -1) {
11885 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11889 if (currentMove > cmailOldMove + 1) {
11890 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11894 if (currentMove < cmailOldMove) {
11895 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11899 if (forwardMostMove > currentMove) {
11900 /* Silently truncate extra moves */
11904 if ( (currentMove == cmailOldMove + 1)
11905 || ( (currentMove == cmailOldMove)
11906 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11907 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11908 if (gameInfo.result != GameUnfinished) {
11909 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11912 if (commentList[currentMove] != NULL) {
11913 cmailCommentList[lastLoadGameNumber - 1]
11914 = StrSave(commentList[currentMove]);
11916 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11918 if (appData.debugMode)
11919 fprintf(debugFP, "Saving %s for game %d\n",
11920 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11922 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11924 f = fopen(string, "w");
11925 if (appData.oldSaveStyle) {
11926 SaveGameOldStyle(f); /* also closes the file */
11928 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11929 f = fopen(string, "w");
11930 SavePosition(f, 0, NULL); /* also closes the file */
11932 fprintf(f, "{--------------\n");
11933 PrintPosition(f, currentMove);
11934 fprintf(f, "--------------}\n\n");
11936 SaveGame(f, 0, NULL); /* also closes the file*/
11939 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11940 nCmailMovesRegistered ++;
11941 } else if (nCmailGames == 1) {
11942 DisplayError(_("You have not made a move yet"), 0);
11953 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11954 FILE *commandOutput;
11955 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11956 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11962 if (! cmailMsgLoaded) {
11963 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11967 if (nCmailGames == nCmailResults) {
11968 DisplayError(_("No unfinished games"), 0);
11972 #if CMAIL_PROHIBIT_REMAIL
11973 if (cmailMailedMove) {
11974 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);
11975 DisplayError(msg, 0);
11980 if (! (cmailMailedMove || RegisterMove())) return;
11982 if ( cmailMailedMove
11983 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11984 snprintf(string, MSG_SIZ, partCommandString,
11985 appData.debugMode ? " -v" : "", appData.cmailGameName);
11986 commandOutput = popen(string, "r");
11988 if (commandOutput == NULL) {
11989 DisplayError(_("Failed to invoke cmail"), 0);
11991 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11992 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11994 if (nBuffers > 1) {
11995 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11996 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11997 nBytes = MSG_SIZ - 1;
11999 (void) memcpy(msg, buffer, nBytes);
12001 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12003 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12004 cmailMailedMove = TRUE; /* Prevent >1 moves */
12007 for (i = 0; i < nCmailGames; i ++) {
12008 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12013 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12015 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12017 appData.cmailGameName,
12019 LoadGameFromFile(buffer, 1, buffer, FALSE);
12020 cmailMsgLoaded = FALSE;
12024 DisplayInformation(msg);
12025 pclose(commandOutput);
12028 if ((*cmailMsg) != '\0') {
12029 DisplayInformation(cmailMsg);
12034 #endif /* !WIN32 */
12043 int prependComma = 0;
12045 char string[MSG_SIZ]; /* Space for game-list */
12048 if (!cmailMsgLoaded) return "";
12050 if (cmailMailedMove) {
12051 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12053 /* Create a list of games left */
12054 snprintf(string, MSG_SIZ, "[");
12055 for (i = 0; i < nCmailGames; i ++) {
12056 if (! ( cmailMoveRegistered[i]
12057 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12058 if (prependComma) {
12059 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12061 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12065 strcat(string, number);
12068 strcat(string, "]");
12070 if (nCmailMovesRegistered + nCmailResults == 0) {
12071 switch (nCmailGames) {
12073 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12077 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12081 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12086 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12088 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12093 if (nCmailResults == nCmailGames) {
12094 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12096 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12101 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12113 if (gameMode == Training)
12114 SetTrainingModeOff();
12117 cmailMsgLoaded = FALSE;
12118 if (appData.icsActive) {
12119 SendToICS(ics_prefix);
12120 SendToICS("refresh\n");
12130 /* Give up on clean exit */
12134 /* Keep trying for clean exit */
12138 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12140 if (telnetISR != NULL) {
12141 RemoveInputSource(telnetISR);
12143 if (icsPR != NoProc) {
12144 DestroyChildProcess(icsPR, TRUE);
12147 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12148 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12150 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12151 /* make sure this other one finishes before killing it! */
12152 if(endingGame) { int count = 0;
12153 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12154 while(endingGame && count++ < 10) DoSleep(1);
12155 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12158 /* Kill off chess programs */
12159 if (first.pr != NoProc) {
12162 DoSleep( appData.delayBeforeQuit );
12163 SendToProgram("quit\n", &first);
12164 DoSleep( appData.delayAfterQuit );
12165 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12167 if (second.pr != NoProc) {
12168 DoSleep( appData.delayBeforeQuit );
12169 SendToProgram("quit\n", &second);
12170 DoSleep( appData.delayAfterQuit );
12171 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12173 if (first.isr != NULL) {
12174 RemoveInputSource(first.isr);
12176 if (second.isr != NULL) {
12177 RemoveInputSource(second.isr);
12180 ShutDownFrontEnd();
12187 if (appData.debugMode)
12188 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12192 if (gameMode == MachinePlaysWhite ||
12193 gameMode == MachinePlaysBlack) {
12196 DisplayBothClocks();
12198 if (gameMode == PlayFromGameFile) {
12199 if (appData.timeDelay >= 0)
12200 AutoPlayGameLoop();
12201 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12202 Reset(FALSE, TRUE);
12203 SendToICS(ics_prefix);
12204 SendToICS("refresh\n");
12205 } else if (currentMove < forwardMostMove) {
12206 ForwardInner(forwardMostMove);
12208 pauseExamInvalid = FALSE;
12210 switch (gameMode) {
12214 pauseExamForwardMostMove = forwardMostMove;
12215 pauseExamInvalid = FALSE;
12218 case IcsPlayingWhite:
12219 case IcsPlayingBlack:
12223 case PlayFromGameFile:
12224 (void) StopLoadGameTimer();
12228 case BeginningOfGame:
12229 if (appData.icsActive) return;
12230 /* else fall through */
12231 case MachinePlaysWhite:
12232 case MachinePlaysBlack:
12233 case TwoMachinesPlay:
12234 if (forwardMostMove == 0)
12235 return; /* don't pause if no one has moved */
12236 if ((gameMode == MachinePlaysWhite &&
12237 !WhiteOnMove(forwardMostMove)) ||
12238 (gameMode == MachinePlaysBlack &&
12239 WhiteOnMove(forwardMostMove))) {
12252 char title[MSG_SIZ];
12254 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12255 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12257 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12258 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12259 parseList[currentMove - 1]);
12262 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12269 char *tags = PGNTags(&gameInfo);
12270 EditTagsPopUp(tags, NULL);
12277 if (appData.noChessProgram || gameMode == AnalyzeMode)
12280 if (gameMode != AnalyzeFile) {
12281 if (!appData.icsEngineAnalyze) {
12283 if (gameMode != EditGame) return;
12285 ResurrectChessProgram();
12286 SendToProgram("analyze\n", &first);
12287 first.analyzing = TRUE;
12288 /*first.maybeThinking = TRUE;*/
12289 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12290 EngineOutputPopUp();
12292 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12297 StartAnalysisClock();
12298 GetTimeMark(&lastNodeCountTime);
12305 if (appData.noChessProgram || gameMode == AnalyzeFile)
12308 if (gameMode != AnalyzeMode) {
12310 if (gameMode != EditGame) return;
12311 ResurrectChessProgram();
12312 SendToProgram("analyze\n", &first);
12313 first.analyzing = TRUE;
12314 /*first.maybeThinking = TRUE;*/
12315 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12316 EngineOutputPopUp();
12318 gameMode = AnalyzeFile;
12323 StartAnalysisClock();
12324 GetTimeMark(&lastNodeCountTime);
12329 MachineWhiteEvent()
12332 char *bookHit = NULL;
12334 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12338 if (gameMode == PlayFromGameFile ||
12339 gameMode == TwoMachinesPlay ||
12340 gameMode == Training ||
12341 gameMode == AnalyzeMode ||
12342 gameMode == EndOfGame)
12345 if (gameMode == EditPosition)
12346 EditPositionDone(TRUE);
12348 if (!WhiteOnMove(currentMove)) {
12349 DisplayError(_("It is not White's turn"), 0);
12353 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12356 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12357 gameMode == AnalyzeFile)
12360 ResurrectChessProgram(); /* in case it isn't running */
12361 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12362 gameMode = MachinePlaysWhite;
12365 gameMode = MachinePlaysWhite;
12369 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12371 if (first.sendName) {
12372 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12373 SendToProgram(buf, &first);
12375 if (first.sendTime) {
12376 if (first.useColors) {
12377 SendToProgram("black\n", &first); /*gnu kludge*/
12379 SendTimeRemaining(&first, TRUE);
12381 if (first.useColors) {
12382 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12384 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12385 SetMachineThinkingEnables();
12386 first.maybeThinking = TRUE;
12390 if (appData.autoFlipView && !flipView) {
12391 flipView = !flipView;
12392 DrawPosition(FALSE, NULL);
12393 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12396 if(bookHit) { // [HGM] book: simulate book reply
12397 static char bookMove[MSG_SIZ]; // a bit generous?
12399 programStats.nodes = programStats.depth = programStats.time =
12400 programStats.score = programStats.got_only_move = 0;
12401 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12403 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12404 strcat(bookMove, bookHit);
12405 HandleMachineMove(bookMove, &first);
12410 MachineBlackEvent()
12413 char *bookHit = NULL;
12415 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12419 if (gameMode == PlayFromGameFile ||
12420 gameMode == TwoMachinesPlay ||
12421 gameMode == Training ||
12422 gameMode == AnalyzeMode ||
12423 gameMode == EndOfGame)
12426 if (gameMode == EditPosition)
12427 EditPositionDone(TRUE);
12429 if (WhiteOnMove(currentMove)) {
12430 DisplayError(_("It is not Black's turn"), 0);
12434 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12437 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12438 gameMode == AnalyzeFile)
12441 ResurrectChessProgram(); /* in case it isn't running */
12442 gameMode = MachinePlaysBlack;
12446 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12448 if (first.sendName) {
12449 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12450 SendToProgram(buf, &first);
12452 if (first.sendTime) {
12453 if (first.useColors) {
12454 SendToProgram("white\n", &first); /*gnu kludge*/
12456 SendTimeRemaining(&first, FALSE);
12458 if (first.useColors) {
12459 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12461 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12462 SetMachineThinkingEnables();
12463 first.maybeThinking = TRUE;
12466 if (appData.autoFlipView && flipView) {
12467 flipView = !flipView;
12468 DrawPosition(FALSE, NULL);
12469 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12471 if(bookHit) { // [HGM] book: simulate book reply
12472 static char bookMove[MSG_SIZ]; // a bit generous?
12474 programStats.nodes = programStats.depth = programStats.time =
12475 programStats.score = programStats.got_only_move = 0;
12476 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12478 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12479 strcat(bookMove, bookHit);
12480 HandleMachineMove(bookMove, &first);
12486 DisplayTwoMachinesTitle()
12489 if (appData.matchGames > 0) {
12490 if (first.twoMachinesColor[0] == 'w') {
12491 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12492 gameInfo.white, gameInfo.black,
12493 first.matchWins, second.matchWins,
12494 matchGame - 1 - (first.matchWins + second.matchWins));
12496 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12497 gameInfo.white, gameInfo.black,
12498 second.matchWins, first.matchWins,
12499 matchGame - 1 - (first.matchWins + second.matchWins));
12502 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12508 SettingsMenuIfReady()
12510 if (second.lastPing != second.lastPong) {
12511 DisplayMessage("", _("Waiting for second chess program"));
12512 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12516 DisplayMessage("", "");
12517 SettingsPopUp(&second);
12521 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12524 if (cps->pr == NULL) {
12525 StartChessProgram(cps);
12526 if (cps->protocolVersion == 1) {
12529 /* kludge: allow timeout for initial "feature" command */
12531 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12532 DisplayMessage("", buf);
12533 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12541 TwoMachinesEvent P((void))
12545 ChessProgramState *onmove;
12546 char *bookHit = NULL;
12547 static int stalling = 0;
12551 if (appData.noChessProgram) return;
12553 switch (gameMode) {
12554 case TwoMachinesPlay:
12556 case MachinePlaysWhite:
12557 case MachinePlaysBlack:
12558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12563 case BeginningOfGame:
12564 case PlayFromGameFile:
12567 if (gameMode != EditGame) return;
12570 EditPositionDone(TRUE);
12581 // forwardMostMove = currentMove;
12582 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12584 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12586 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12587 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12588 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12592 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12593 SendToProgram("force\n", &second);
12595 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12598 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12599 if(appData.matchPause>10000 || appData.matchPause<10)
12600 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12601 wait = SubtractTimeMarks(&now, &pauseStart);
12602 if(wait < appData.matchPause) {
12603 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12607 DisplayMessage("", "");
12608 if (startedFromSetupPosition) {
12609 SendBoard(&second, backwardMostMove);
12610 if (appData.debugMode) {
12611 fprintf(debugFP, "Two Machines\n");
12614 for (i = backwardMostMove; i < forwardMostMove; i++) {
12615 SendMoveToProgram(i, &second);
12618 gameMode = TwoMachinesPlay;
12622 DisplayTwoMachinesTitle();
12624 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12629 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12630 SendToProgram(first.computerString, &first);
12631 if (first.sendName) {
12632 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12633 SendToProgram(buf, &first);
12635 SendToProgram(second.computerString, &second);
12636 if (second.sendName) {
12637 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12638 SendToProgram(buf, &second);
12642 if (!first.sendTime || !second.sendTime) {
12643 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12644 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12646 if (onmove->sendTime) {
12647 if (onmove->useColors) {
12648 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12650 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12652 if (onmove->useColors) {
12653 SendToProgram(onmove->twoMachinesColor, onmove);
12655 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12656 // SendToProgram("go\n", onmove);
12657 onmove->maybeThinking = TRUE;
12658 SetMachineThinkingEnables();
12662 if(bookHit) { // [HGM] book: simulate book reply
12663 static char bookMove[MSG_SIZ]; // a bit generous?
12665 programStats.nodes = programStats.depth = programStats.time =
12666 programStats.score = programStats.got_only_move = 0;
12667 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12669 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12670 strcat(bookMove, bookHit);
12671 savedMessage = bookMove; // args for deferred call
12672 savedState = onmove;
12673 ScheduleDelayedEvent(DeferredBookMove, 1);
12680 if (gameMode == Training) {
12681 SetTrainingModeOff();
12682 gameMode = PlayFromGameFile;
12683 DisplayMessage("", _("Training mode off"));
12685 gameMode = Training;
12686 animateTraining = appData.animate;
12688 /* make sure we are not already at the end of the game */
12689 if (currentMove < forwardMostMove) {
12690 SetTrainingModeOn();
12691 DisplayMessage("", _("Training mode on"));
12693 gameMode = PlayFromGameFile;
12694 DisplayError(_("Already at end of game"), 0);
12703 if (!appData.icsActive) return;
12704 switch (gameMode) {
12705 case IcsPlayingWhite:
12706 case IcsPlayingBlack:
12709 case BeginningOfGame:
12717 EditPositionDone(TRUE);
12730 gameMode = IcsIdle;
12741 switch (gameMode) {
12743 SetTrainingModeOff();
12745 case MachinePlaysWhite:
12746 case MachinePlaysBlack:
12747 case BeginningOfGame:
12748 SendToProgram("force\n", &first);
12749 SetUserThinkingEnables();
12751 case PlayFromGameFile:
12752 (void) StopLoadGameTimer();
12753 if (gameFileFP != NULL) {
12758 EditPositionDone(TRUE);
12763 SendToProgram("force\n", &first);
12765 case TwoMachinesPlay:
12766 GameEnds(EndOfFile, NULL, GE_PLAYER);
12767 ResurrectChessProgram();
12768 SetUserThinkingEnables();
12771 ResurrectChessProgram();
12773 case IcsPlayingBlack:
12774 case IcsPlayingWhite:
12775 DisplayError(_("Warning: You are still playing a game"), 0);
12778 DisplayError(_("Warning: You are still observing a game"), 0);
12781 DisplayError(_("Warning: You are still examining a game"), 0);
12792 first.offeredDraw = second.offeredDraw = 0;
12794 if (gameMode == PlayFromGameFile) {
12795 whiteTimeRemaining = timeRemaining[0][currentMove];
12796 blackTimeRemaining = timeRemaining[1][currentMove];
12800 if (gameMode == MachinePlaysWhite ||
12801 gameMode == MachinePlaysBlack ||
12802 gameMode == TwoMachinesPlay ||
12803 gameMode == EndOfGame) {
12804 i = forwardMostMove;
12805 while (i > currentMove) {
12806 SendToProgram("undo\n", &first);
12809 whiteTimeRemaining = timeRemaining[0][currentMove];
12810 blackTimeRemaining = timeRemaining[1][currentMove];
12811 DisplayBothClocks();
12812 if (whiteFlag || blackFlag) {
12813 whiteFlag = blackFlag = 0;
12818 gameMode = EditGame;
12825 EditPositionEvent()
12827 if (gameMode == EditPosition) {
12833 if (gameMode != EditGame) return;
12835 gameMode = EditPosition;
12838 if (currentMove > 0)
12839 CopyBoard(boards[0], boards[currentMove]);
12841 blackPlaysFirst = !WhiteOnMove(currentMove);
12843 currentMove = forwardMostMove = backwardMostMove = 0;
12844 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12851 /* [DM] icsEngineAnalyze - possible call from other functions */
12852 if (appData.icsEngineAnalyze) {
12853 appData.icsEngineAnalyze = FALSE;
12855 DisplayMessage("",_("Close ICS engine analyze..."));
12857 if (first.analysisSupport && first.analyzing) {
12858 SendToProgram("exit\n", &first);
12859 first.analyzing = FALSE;
12861 thinkOutput[0] = NULLCHAR;
12865 EditPositionDone(Boolean fakeRights)
12867 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12869 startedFromSetupPosition = TRUE;
12870 InitChessProgram(&first, FALSE);
12871 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12872 boards[0][EP_STATUS] = EP_NONE;
12873 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12874 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12875 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12876 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12877 } else boards[0][CASTLING][2] = NoRights;
12878 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12879 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12880 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12881 } else boards[0][CASTLING][5] = NoRights;
12883 SendToProgram("force\n", &first);
12884 if (blackPlaysFirst) {
12885 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12886 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12887 currentMove = forwardMostMove = backwardMostMove = 1;
12888 CopyBoard(boards[1], boards[0]);
12890 currentMove = forwardMostMove = backwardMostMove = 0;
12892 SendBoard(&first, forwardMostMove);
12893 if (appData.debugMode) {
12894 fprintf(debugFP, "EditPosDone\n");
12897 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12898 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12899 gameMode = EditGame;
12901 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12902 ClearHighlights(); /* [AS] */
12905 /* Pause for `ms' milliseconds */
12906 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12916 } while (SubtractTimeMarks(&m2, &m1) < ms);
12919 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12921 SendMultiLineToICS(buf)
12924 char temp[MSG_SIZ+1], *p;
12931 strncpy(temp, buf, len);
12936 if (*p == '\n' || *p == '\r')
12941 strcat(temp, "\n");
12943 SendToPlayer(temp, strlen(temp));
12947 SetWhiteToPlayEvent()
12949 if (gameMode == EditPosition) {
12950 blackPlaysFirst = FALSE;
12951 DisplayBothClocks(); /* works because currentMove is 0 */
12952 } else if (gameMode == IcsExamining) {
12953 SendToICS(ics_prefix);
12954 SendToICS("tomove white\n");
12959 SetBlackToPlayEvent()
12961 if (gameMode == EditPosition) {
12962 blackPlaysFirst = TRUE;
12963 currentMove = 1; /* kludge */
12964 DisplayBothClocks();
12966 } else if (gameMode == IcsExamining) {
12967 SendToICS(ics_prefix);
12968 SendToICS("tomove black\n");
12973 EditPositionMenuEvent(selection, x, y)
12974 ChessSquare selection;
12978 ChessSquare piece = boards[0][y][x];
12980 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12982 switch (selection) {
12984 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12985 SendToICS(ics_prefix);
12986 SendToICS("bsetup clear\n");
12987 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12988 SendToICS(ics_prefix);
12989 SendToICS("clearboard\n");
12991 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12992 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12993 for (y = 0; y < BOARD_HEIGHT; y++) {
12994 if (gameMode == IcsExamining) {
12995 if (boards[currentMove][y][x] != EmptySquare) {
12996 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13001 boards[0][y][x] = p;
13006 if (gameMode == EditPosition) {
13007 DrawPosition(FALSE, boards[0]);
13012 SetWhiteToPlayEvent();
13016 SetBlackToPlayEvent();
13020 if (gameMode == IcsExamining) {
13021 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13022 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13025 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13026 if(x == BOARD_LEFT-2) {
13027 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13028 boards[0][y][1] = 0;
13030 if(x == BOARD_RGHT+1) {
13031 if(y >= gameInfo.holdingsSize) break;
13032 boards[0][y][BOARD_WIDTH-2] = 0;
13035 boards[0][y][x] = EmptySquare;
13036 DrawPosition(FALSE, boards[0]);
13041 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13042 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13043 selection = (ChessSquare) (PROMOTED piece);
13044 } else if(piece == EmptySquare) selection = WhiteSilver;
13045 else selection = (ChessSquare)((int)piece - 1);
13049 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13050 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13051 selection = (ChessSquare) (DEMOTED piece);
13052 } else if(piece == EmptySquare) selection = BlackSilver;
13053 else selection = (ChessSquare)((int)piece + 1);
13058 if(gameInfo.variant == VariantShatranj ||
13059 gameInfo.variant == VariantXiangqi ||
13060 gameInfo.variant == VariantCourier ||
13061 gameInfo.variant == VariantMakruk )
13062 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13067 if(gameInfo.variant == VariantXiangqi)
13068 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13069 if(gameInfo.variant == VariantKnightmate)
13070 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13073 if (gameMode == IcsExamining) {
13074 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13075 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13076 PieceToChar(selection), AAA + x, ONE + y);
13079 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13081 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13082 n = PieceToNumber(selection - BlackPawn);
13083 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13084 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13085 boards[0][BOARD_HEIGHT-1-n][1]++;
13087 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13088 n = PieceToNumber(selection);
13089 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13090 boards[0][n][BOARD_WIDTH-1] = selection;
13091 boards[0][n][BOARD_WIDTH-2]++;
13094 boards[0][y][x] = selection;
13095 DrawPosition(TRUE, boards[0]);
13103 DropMenuEvent(selection, x, y)
13104 ChessSquare selection;
13107 ChessMove moveType;
13109 switch (gameMode) {
13110 case IcsPlayingWhite:
13111 case MachinePlaysBlack:
13112 if (!WhiteOnMove(currentMove)) {
13113 DisplayMoveError(_("It is Black's turn"));
13116 moveType = WhiteDrop;
13118 case IcsPlayingBlack:
13119 case MachinePlaysWhite:
13120 if (WhiteOnMove(currentMove)) {
13121 DisplayMoveError(_("It is White's turn"));
13124 moveType = BlackDrop;
13127 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13133 if (moveType == BlackDrop && selection < BlackPawn) {
13134 selection = (ChessSquare) ((int) selection
13135 + (int) BlackPawn - (int) WhitePawn);
13137 if (boards[currentMove][y][x] != EmptySquare) {
13138 DisplayMoveError(_("That square is occupied"));
13142 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13148 /* Accept a pending offer of any kind from opponent */
13150 if (appData.icsActive) {
13151 SendToICS(ics_prefix);
13152 SendToICS("accept\n");
13153 } else if (cmailMsgLoaded) {
13154 if (currentMove == cmailOldMove &&
13155 commentList[cmailOldMove] != NULL &&
13156 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13157 "Black offers a draw" : "White offers a draw")) {
13159 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13160 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13162 DisplayError(_("There is no pending offer on this move"), 0);
13163 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13166 /* Not used for offers from chess program */
13173 /* Decline a pending offer of any kind from opponent */
13175 if (appData.icsActive) {
13176 SendToICS(ics_prefix);
13177 SendToICS("decline\n");
13178 } else if (cmailMsgLoaded) {
13179 if (currentMove == cmailOldMove &&
13180 commentList[cmailOldMove] != NULL &&
13181 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13182 "Black offers a draw" : "White offers a draw")) {
13184 AppendComment(cmailOldMove, "Draw declined", TRUE);
13185 DisplayComment(cmailOldMove - 1, "Draw declined");
13188 DisplayError(_("There is no pending offer on this move"), 0);
13191 /* Not used for offers from chess program */
13198 /* Issue ICS rematch command */
13199 if (appData.icsActive) {
13200 SendToICS(ics_prefix);
13201 SendToICS("rematch\n");
13208 /* Call your opponent's flag (claim a win on time) */
13209 if (appData.icsActive) {
13210 SendToICS(ics_prefix);
13211 SendToICS("flag\n");
13213 switch (gameMode) {
13216 case MachinePlaysWhite:
13219 GameEnds(GameIsDrawn, "Both players ran out of time",
13222 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13224 DisplayError(_("Your opponent is not out of time"), 0);
13227 case MachinePlaysBlack:
13230 GameEnds(GameIsDrawn, "Both players ran out of time",
13233 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13235 DisplayError(_("Your opponent is not out of time"), 0);
13243 ClockClick(int which)
13244 { // [HGM] code moved to back-end from winboard.c
13245 if(which) { // black clock
13246 if (gameMode == EditPosition || gameMode == IcsExamining) {
13247 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13248 SetBlackToPlayEvent();
13249 } else if (gameMode == EditGame || shiftKey) {
13250 AdjustClock(which, -1);
13251 } else if (gameMode == IcsPlayingWhite ||
13252 gameMode == MachinePlaysBlack) {
13255 } else { // white clock
13256 if (gameMode == EditPosition || gameMode == IcsExamining) {
13257 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13258 SetWhiteToPlayEvent();
13259 } else if (gameMode == EditGame || shiftKey) {
13260 AdjustClock(which, -1);
13261 } else if (gameMode == IcsPlayingBlack ||
13262 gameMode == MachinePlaysWhite) {
13271 /* Offer draw or accept pending draw offer from opponent */
13273 if (appData.icsActive) {
13274 /* Note: tournament rules require draw offers to be
13275 made after you make your move but before you punch
13276 your clock. Currently ICS doesn't let you do that;
13277 instead, you immediately punch your clock after making
13278 a move, but you can offer a draw at any time. */
13280 SendToICS(ics_prefix);
13281 SendToICS("draw\n");
13282 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13283 } else if (cmailMsgLoaded) {
13284 if (currentMove == cmailOldMove &&
13285 commentList[cmailOldMove] != NULL &&
13286 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13287 "Black offers a draw" : "White offers a draw")) {
13288 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13289 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13290 } else if (currentMove == cmailOldMove + 1) {
13291 char *offer = WhiteOnMove(cmailOldMove) ?
13292 "White offers a draw" : "Black offers a draw";
13293 AppendComment(currentMove, offer, TRUE);
13294 DisplayComment(currentMove - 1, offer);
13295 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13297 DisplayError(_("You must make your move before offering a draw"), 0);
13298 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13300 } else if (first.offeredDraw) {
13301 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13303 if (first.sendDrawOffers) {
13304 SendToProgram("draw\n", &first);
13305 userOfferedDraw = TRUE;
13313 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13315 if (appData.icsActive) {
13316 SendToICS(ics_prefix);
13317 SendToICS("adjourn\n");
13319 /* Currently GNU Chess doesn't offer or accept Adjourns */
13327 /* Offer Abort or accept pending Abort offer from opponent */
13329 if (appData.icsActive) {
13330 SendToICS(ics_prefix);
13331 SendToICS("abort\n");
13333 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13340 /* Resign. You can do this even if it's not your turn. */
13342 if (appData.icsActive) {
13343 SendToICS(ics_prefix);
13344 SendToICS("resign\n");
13346 switch (gameMode) {
13347 case MachinePlaysWhite:
13348 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13350 case MachinePlaysBlack:
13351 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13354 if (cmailMsgLoaded) {
13356 if (WhiteOnMove(cmailOldMove)) {
13357 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13359 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13361 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13372 StopObservingEvent()
13374 /* Stop observing current games */
13375 SendToICS(ics_prefix);
13376 SendToICS("unobserve\n");
13380 StopExaminingEvent()
13382 /* Stop observing current game */
13383 SendToICS(ics_prefix);
13384 SendToICS("unexamine\n");
13388 ForwardInner(target)
13393 if (appData.debugMode)
13394 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13395 target, currentMove, forwardMostMove);
13397 if (gameMode == EditPosition)
13400 if (gameMode == PlayFromGameFile && !pausing)
13403 if (gameMode == IcsExamining && pausing)
13404 limit = pauseExamForwardMostMove;
13406 limit = forwardMostMove;
13408 if (target > limit) target = limit;
13410 if (target > 0 && moveList[target - 1][0]) {
13411 int fromX, fromY, toX, toY;
13412 toX = moveList[target - 1][2] - AAA;
13413 toY = moveList[target - 1][3] - ONE;
13414 if (moveList[target - 1][1] == '@') {
13415 if (appData.highlightLastMove) {
13416 SetHighlights(-1, -1, toX, toY);
13419 fromX = moveList[target - 1][0] - AAA;
13420 fromY = moveList[target - 1][1] - ONE;
13421 if (target == currentMove + 1) {
13422 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13424 if (appData.highlightLastMove) {
13425 SetHighlights(fromX, fromY, toX, toY);
13429 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13430 gameMode == Training || gameMode == PlayFromGameFile ||
13431 gameMode == AnalyzeFile) {
13432 while (currentMove < target) {
13433 SendMoveToProgram(currentMove++, &first);
13436 currentMove = target;
13439 if (gameMode == EditGame || gameMode == EndOfGame) {
13440 whiteTimeRemaining = timeRemaining[0][currentMove];
13441 blackTimeRemaining = timeRemaining[1][currentMove];
13443 DisplayBothClocks();
13444 DisplayMove(currentMove - 1);
13445 DrawPosition(FALSE, boards[currentMove]);
13446 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13447 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13448 DisplayComment(currentMove - 1, commentList[currentMove]);
13456 if (gameMode == IcsExamining && !pausing) {
13457 SendToICS(ics_prefix);
13458 SendToICS("forward\n");
13460 ForwardInner(currentMove + 1);
13467 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13468 /* to optimze, we temporarily turn off analysis mode while we feed
13469 * the remaining moves to the engine. Otherwise we get analysis output
13472 if (first.analysisSupport) {
13473 SendToProgram("exit\nforce\n", &first);
13474 first.analyzing = FALSE;
13478 if (gameMode == IcsExamining && !pausing) {
13479 SendToICS(ics_prefix);
13480 SendToICS("forward 999999\n");
13482 ForwardInner(forwardMostMove);
13485 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13486 /* we have fed all the moves, so reactivate analysis mode */
13487 SendToProgram("analyze\n", &first);
13488 first.analyzing = TRUE;
13489 /*first.maybeThinking = TRUE;*/
13490 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13495 BackwardInner(target)
13498 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13500 if (appData.debugMode)
13501 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13502 target, currentMove, forwardMostMove);
13504 if (gameMode == EditPosition) return;
13505 if (currentMove <= backwardMostMove) {
13507 DrawPosition(full_redraw, boards[currentMove]);
13510 if (gameMode == PlayFromGameFile && !pausing)
13513 if (moveList[target][0]) {
13514 int fromX, fromY, toX, toY;
13515 toX = moveList[target][2] - AAA;
13516 toY = moveList[target][3] - ONE;
13517 if (moveList[target][1] == '@') {
13518 if (appData.highlightLastMove) {
13519 SetHighlights(-1, -1, toX, toY);
13522 fromX = moveList[target][0] - AAA;
13523 fromY = moveList[target][1] - ONE;
13524 if (target == currentMove - 1) {
13525 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13527 if (appData.highlightLastMove) {
13528 SetHighlights(fromX, fromY, toX, toY);
13532 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13533 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13534 while (currentMove > target) {
13535 SendToProgram("undo\n", &first);
13539 currentMove = target;
13542 if (gameMode == EditGame || gameMode == EndOfGame) {
13543 whiteTimeRemaining = timeRemaining[0][currentMove];
13544 blackTimeRemaining = timeRemaining[1][currentMove];
13546 DisplayBothClocks();
13547 DisplayMove(currentMove - 1);
13548 DrawPosition(full_redraw, boards[currentMove]);
13549 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13550 // [HGM] PV info: routine tests if comment empty
13551 DisplayComment(currentMove - 1, commentList[currentMove]);
13557 if (gameMode == IcsExamining && !pausing) {
13558 SendToICS(ics_prefix);
13559 SendToICS("backward\n");
13561 BackwardInner(currentMove - 1);
13568 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13569 /* to optimize, we temporarily turn off analysis mode while we undo
13570 * all the moves. Otherwise we get analysis output after each undo.
13572 if (first.analysisSupport) {
13573 SendToProgram("exit\nforce\n", &first);
13574 first.analyzing = FALSE;
13578 if (gameMode == IcsExamining && !pausing) {
13579 SendToICS(ics_prefix);
13580 SendToICS("backward 999999\n");
13582 BackwardInner(backwardMostMove);
13585 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13586 /* we have fed all the moves, so reactivate analysis mode */
13587 SendToProgram("analyze\n", &first);
13588 first.analyzing = TRUE;
13589 /*first.maybeThinking = TRUE;*/
13590 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13597 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13598 if (to >= forwardMostMove) to = forwardMostMove;
13599 if (to <= backwardMostMove) to = backwardMostMove;
13600 if (to < currentMove) {
13608 RevertEvent(Boolean annotate)
13610 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13613 if (gameMode != IcsExamining) {
13614 DisplayError(_("You are not examining a game"), 0);
13618 DisplayError(_("You can't revert while pausing"), 0);
13621 SendToICS(ics_prefix);
13622 SendToICS("revert\n");
13628 switch (gameMode) {
13629 case MachinePlaysWhite:
13630 case MachinePlaysBlack:
13631 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13632 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13635 if (forwardMostMove < 2) return;
13636 currentMove = forwardMostMove = forwardMostMove - 2;
13637 whiteTimeRemaining = timeRemaining[0][currentMove];
13638 blackTimeRemaining = timeRemaining[1][currentMove];
13639 DisplayBothClocks();
13640 DisplayMove(currentMove - 1);
13641 ClearHighlights();/*!! could figure this out*/
13642 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13643 SendToProgram("remove\n", &first);
13644 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13647 case BeginningOfGame:
13651 case IcsPlayingWhite:
13652 case IcsPlayingBlack:
13653 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13654 SendToICS(ics_prefix);
13655 SendToICS("takeback 2\n");
13657 SendToICS(ics_prefix);
13658 SendToICS("takeback 1\n");
13667 ChessProgramState *cps;
13669 switch (gameMode) {
13670 case MachinePlaysWhite:
13671 if (!WhiteOnMove(forwardMostMove)) {
13672 DisplayError(_("It is your turn"), 0);
13677 case MachinePlaysBlack:
13678 if (WhiteOnMove(forwardMostMove)) {
13679 DisplayError(_("It is your turn"), 0);
13684 case TwoMachinesPlay:
13685 if (WhiteOnMove(forwardMostMove) ==
13686 (first.twoMachinesColor[0] == 'w')) {
13692 case BeginningOfGame:
13696 SendToProgram("?\n", cps);
13700 TruncateGameEvent()
13703 if (gameMode != EditGame) return;
13710 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13711 if (forwardMostMove > currentMove) {
13712 if (gameInfo.resultDetails != NULL) {
13713 free(gameInfo.resultDetails);
13714 gameInfo.resultDetails = NULL;
13715 gameInfo.result = GameUnfinished;
13717 forwardMostMove = currentMove;
13718 HistorySet(parseList, backwardMostMove, forwardMostMove,
13726 if (appData.noChessProgram) return;
13727 switch (gameMode) {
13728 case MachinePlaysWhite:
13729 if (WhiteOnMove(forwardMostMove)) {
13730 DisplayError(_("Wait until your turn"), 0);
13734 case BeginningOfGame:
13735 case MachinePlaysBlack:
13736 if (!WhiteOnMove(forwardMostMove)) {
13737 DisplayError(_("Wait until your turn"), 0);
13742 DisplayError(_("No hint available"), 0);
13745 SendToProgram("hint\n", &first);
13746 hintRequested = TRUE;
13752 if (appData.noChessProgram) return;
13753 switch (gameMode) {
13754 case MachinePlaysWhite:
13755 if (WhiteOnMove(forwardMostMove)) {
13756 DisplayError(_("Wait until your turn"), 0);
13760 case BeginningOfGame:
13761 case MachinePlaysBlack:
13762 if (!WhiteOnMove(forwardMostMove)) {
13763 DisplayError(_("Wait until your turn"), 0);
13768 EditPositionDone(TRUE);
13770 case TwoMachinesPlay:
13775 SendToProgram("bk\n", &first);
13776 bookOutput[0] = NULLCHAR;
13777 bookRequested = TRUE;
13783 char *tags = PGNTags(&gameInfo);
13784 TagsPopUp(tags, CmailMsg());
13788 /* end button procedures */
13791 PrintPosition(fp, move)
13797 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13798 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13799 char c = PieceToChar(boards[move][i][j]);
13800 fputc(c == 'x' ? '.' : c, fp);
13801 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13804 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13805 fprintf(fp, "white to play\n");
13807 fprintf(fp, "black to play\n");
13814 if (gameInfo.white != NULL) {
13815 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13821 /* Find last component of program's own name, using some heuristics */
13823 TidyProgramName(prog, host, buf)
13824 char *prog, *host, buf[MSG_SIZ];
13827 int local = (strcmp(host, "localhost") == 0);
13828 while (!local && (p = strchr(prog, ';')) != NULL) {
13830 while (*p == ' ') p++;
13833 if (*prog == '"' || *prog == '\'') {
13834 q = strchr(prog + 1, *prog);
13836 q = strchr(prog, ' ');
13838 if (q == NULL) q = prog + strlen(prog);
13840 while (p >= prog && *p != '/' && *p != '\\') p--;
13842 if(p == prog && *p == '"') p++;
13843 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13844 memcpy(buf, p, q - p);
13845 buf[q - p] = NULLCHAR;
13853 TimeControlTagValue()
13856 if (!appData.clockMode) {
13857 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13858 } else if (movesPerSession > 0) {
13859 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13860 } else if (timeIncrement == 0) {
13861 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13863 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13865 return StrSave(buf);
13871 /* This routine is used only for certain modes */
13872 VariantClass v = gameInfo.variant;
13873 ChessMove r = GameUnfinished;
13876 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13877 r = gameInfo.result;
13878 p = gameInfo.resultDetails;
13879 gameInfo.resultDetails = NULL;
13881 ClearGameInfo(&gameInfo);
13882 gameInfo.variant = v;
13884 switch (gameMode) {
13885 case MachinePlaysWhite:
13886 gameInfo.event = StrSave( appData.pgnEventHeader );
13887 gameInfo.site = StrSave(HostName());
13888 gameInfo.date = PGNDate();
13889 gameInfo.round = StrSave("-");
13890 gameInfo.white = StrSave(first.tidy);
13891 gameInfo.black = StrSave(UserName());
13892 gameInfo.timeControl = TimeControlTagValue();
13895 case MachinePlaysBlack:
13896 gameInfo.event = StrSave( appData.pgnEventHeader );
13897 gameInfo.site = StrSave(HostName());
13898 gameInfo.date = PGNDate();
13899 gameInfo.round = StrSave("-");
13900 gameInfo.white = StrSave(UserName());
13901 gameInfo.black = StrSave(first.tidy);
13902 gameInfo.timeControl = TimeControlTagValue();
13905 case TwoMachinesPlay:
13906 gameInfo.event = StrSave( appData.pgnEventHeader );
13907 gameInfo.site = StrSave(HostName());
13908 gameInfo.date = PGNDate();
13911 snprintf(buf, MSG_SIZ, "%d", roundNr);
13912 gameInfo.round = StrSave(buf);
13914 gameInfo.round = StrSave("-");
13916 if (first.twoMachinesColor[0] == 'w') {
13917 gameInfo.white = StrSave(first.tidy);
13918 gameInfo.black = StrSave(second.tidy);
13920 gameInfo.white = StrSave(second.tidy);
13921 gameInfo.black = StrSave(first.tidy);
13923 gameInfo.timeControl = TimeControlTagValue();
13927 gameInfo.event = StrSave("Edited game");
13928 gameInfo.site = StrSave(HostName());
13929 gameInfo.date = PGNDate();
13930 gameInfo.round = StrSave("-");
13931 gameInfo.white = StrSave("-");
13932 gameInfo.black = StrSave("-");
13933 gameInfo.result = r;
13934 gameInfo.resultDetails = p;
13938 gameInfo.event = StrSave("Edited position");
13939 gameInfo.site = StrSave(HostName());
13940 gameInfo.date = PGNDate();
13941 gameInfo.round = StrSave("-");
13942 gameInfo.white = StrSave("-");
13943 gameInfo.black = StrSave("-");
13946 case IcsPlayingWhite:
13947 case IcsPlayingBlack:
13952 case PlayFromGameFile:
13953 gameInfo.event = StrSave("Game from non-PGN file");
13954 gameInfo.site = StrSave(HostName());
13955 gameInfo.date = PGNDate();
13956 gameInfo.round = StrSave("-");
13957 gameInfo.white = StrSave("?");
13958 gameInfo.black = StrSave("?");
13967 ReplaceComment(index, text)
13975 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13976 pvInfoList[index-1].depth == len &&
13977 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13978 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13979 while (*text == '\n') text++;
13980 len = strlen(text);
13981 while (len > 0 && text[len - 1] == '\n') len--;
13983 if (commentList[index] != NULL)
13984 free(commentList[index]);
13987 commentList[index] = NULL;
13990 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13991 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13992 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13993 commentList[index] = (char *) malloc(len + 2);
13994 strncpy(commentList[index], text, len);
13995 commentList[index][len] = '\n';
13996 commentList[index][len + 1] = NULLCHAR;
13998 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14000 commentList[index] = (char *) malloc(len + 7);
14001 safeStrCpy(commentList[index], "{\n", 3);
14002 safeStrCpy(commentList[index]+2, text, len+1);
14003 commentList[index][len+2] = NULLCHAR;
14004 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14005 strcat(commentList[index], "\n}\n");
14019 if (ch == '\r') continue;
14021 } while (ch != '\0');
14025 AppendComment(index, text, addBraces)
14028 Boolean addBraces; // [HGM] braces: tells if we should add {}
14033 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14034 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14037 while (*text == '\n') text++;
14038 len = strlen(text);
14039 while (len > 0 && text[len - 1] == '\n') len--;
14041 if (len == 0) return;
14043 if (commentList[index] != NULL) {
14044 old = commentList[index];
14045 oldlen = strlen(old);
14046 while(commentList[index][oldlen-1] == '\n')
14047 commentList[index][--oldlen] = NULLCHAR;
14048 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14049 safeStrCpy(commentList[index], old, oldlen + len + 6);
14051 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14052 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14053 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14054 while (*text == '\n') { text++; len--; }
14055 commentList[index][--oldlen] = NULLCHAR;
14057 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14058 else strcat(commentList[index], "\n");
14059 strcat(commentList[index], text);
14060 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14061 else strcat(commentList[index], "\n");
14063 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14065 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14066 else commentList[index][0] = NULLCHAR;
14067 strcat(commentList[index], text);
14068 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14069 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14073 static char * FindStr( char * text, char * sub_text )
14075 char * result = strstr( text, sub_text );
14077 if( result != NULL ) {
14078 result += strlen( sub_text );
14084 /* [AS] Try to extract PV info from PGN comment */
14085 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14086 char *GetInfoFromComment( int index, char * text )
14088 char * sep = text, *p;
14090 if( text != NULL && index > 0 ) {
14093 int time = -1, sec = 0, deci;
14094 char * s_eval = FindStr( text, "[%eval " );
14095 char * s_emt = FindStr( text, "[%emt " );
14097 if( s_eval != NULL || s_emt != NULL ) {
14101 if( s_eval != NULL ) {
14102 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14106 if( delim != ']' ) {
14111 if( s_emt != NULL ) {
14116 /* We expect something like: [+|-]nnn.nn/dd */
14119 if(*text != '{') return text; // [HGM] braces: must be normal comment
14121 sep = strchr( text, '/' );
14122 if( sep == NULL || sep < (text+4) ) {
14127 if(p[1] == '(') { // comment starts with PV
14128 p = strchr(p, ')'); // locate end of PV
14129 if(p == NULL || sep < p+5) return text;
14130 // at this point we have something like "{(.*) +0.23/6 ..."
14131 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14132 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14133 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14135 time = -1; sec = -1; deci = -1;
14136 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14137 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14138 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14139 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14143 if( score_lo < 0 || score_lo >= 100 ) {
14147 if(sec >= 0) time = 600*time + 10*sec; else
14148 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14150 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14152 /* [HGM] PV time: now locate end of PV info */
14153 while( *++sep >= '0' && *sep <= '9'); // strip depth
14155 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14157 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14159 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14160 while(*sep == ' ') sep++;
14171 pvInfoList[index-1].depth = depth;
14172 pvInfoList[index-1].score = score;
14173 pvInfoList[index-1].time = 10*time; // centi-sec
14174 if(*sep == '}') *sep = 0; else *--sep = '{';
14175 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14181 SendToProgram(message, cps)
14183 ChessProgramState *cps;
14185 int count, outCount, error;
14188 if (cps->pr == NULL) return;
14191 if (appData.debugMode) {
14194 fprintf(debugFP, "%ld >%-6s: %s",
14195 SubtractTimeMarks(&now, &programStartTime),
14196 cps->which, message);
14199 count = strlen(message);
14200 outCount = OutputToProcess(cps->pr, message, count, &error);
14201 if (outCount < count && !exiting
14202 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14203 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14204 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14205 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14206 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14207 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14208 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14209 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14211 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14212 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14213 gameInfo.result = res;
14215 gameInfo.resultDetails = StrSave(buf);
14217 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14218 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14223 ReceiveFromProgram(isr, closure, message, count, error)
14224 InputSourceRef isr;
14232 ChessProgramState *cps = (ChessProgramState *)closure;
14234 if (isr != cps->isr) return; /* Killed intentionally */
14237 RemoveInputSource(cps->isr);
14238 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14239 _(cps->which), cps->program);
14240 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14241 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14242 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14243 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14244 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14246 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14247 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14248 gameInfo.result = res;
14250 gameInfo.resultDetails = StrSave(buf);
14252 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14253 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14255 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14256 _(cps->which), cps->program);
14257 RemoveInputSource(cps->isr);
14259 /* [AS] Program is misbehaving badly... kill it */
14260 if( count == -2 ) {
14261 DestroyChildProcess( cps->pr, 9 );
14265 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14270 if ((end_str = strchr(message, '\r')) != NULL)
14271 *end_str = NULLCHAR;
14272 if ((end_str = strchr(message, '\n')) != NULL)
14273 *end_str = NULLCHAR;
14275 if (appData.debugMode) {
14276 TimeMark now; int print = 1;
14277 char *quote = ""; char c; int i;
14279 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14280 char start = message[0];
14281 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14282 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14283 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14284 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14285 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14286 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14287 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14288 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14289 sscanf(message, "hint: %c", &c)!=1 &&
14290 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14291 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14292 print = (appData.engineComments >= 2);
14294 message[0] = start; // restore original message
14298 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14299 SubtractTimeMarks(&now, &programStartTime), cps->which,
14305 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14306 if (appData.icsEngineAnalyze) {
14307 if (strstr(message, "whisper") != NULL ||
14308 strstr(message, "kibitz") != NULL ||
14309 strstr(message, "tellics") != NULL) return;
14312 HandleMachineMove(message, cps);
14317 SendTimeControl(cps, mps, tc, inc, sd, st)
14318 ChessProgramState *cps;
14319 int mps, inc, sd, st;
14325 if( timeControl_2 > 0 ) {
14326 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14327 tc = timeControl_2;
14330 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14331 inc /= cps->timeOdds;
14332 st /= cps->timeOdds;
14334 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14337 /* Set exact time per move, normally using st command */
14338 if (cps->stKludge) {
14339 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14341 if (seconds == 0) {
14342 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14344 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14347 snprintf(buf, MSG_SIZ, "st %d\n", st);
14350 /* Set conventional or incremental time control, using level command */
14351 if (seconds == 0) {
14352 /* Note old gnuchess bug -- minutes:seconds used to not work.
14353 Fixed in later versions, but still avoid :seconds
14354 when seconds is 0. */
14355 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14357 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14358 seconds, inc/1000.);
14361 SendToProgram(buf, cps);
14363 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14364 /* Orthogonally, limit search to given depth */
14366 if (cps->sdKludge) {
14367 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14369 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14371 SendToProgram(buf, cps);
14374 if(cps->nps >= 0) { /* [HGM] nps */
14375 if(cps->supportsNPS == FALSE)
14376 cps->nps = -1; // don't use if engine explicitly says not supported!
14378 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14379 SendToProgram(buf, cps);
14384 ChessProgramState *WhitePlayer()
14385 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14387 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14388 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14394 SendTimeRemaining(cps, machineWhite)
14395 ChessProgramState *cps;
14396 int /*boolean*/ machineWhite;
14398 char message[MSG_SIZ];
14401 /* Note: this routine must be called when the clocks are stopped
14402 or when they have *just* been set or switched; otherwise
14403 it will be off by the time since the current tick started.
14405 if (machineWhite) {
14406 time = whiteTimeRemaining / 10;
14407 otime = blackTimeRemaining / 10;
14409 time = blackTimeRemaining / 10;
14410 otime = whiteTimeRemaining / 10;
14412 /* [HGM] translate opponent's time by time-odds factor */
14413 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14414 if (appData.debugMode) {
14415 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14418 if (time <= 0) time = 1;
14419 if (otime <= 0) otime = 1;
14421 snprintf(message, MSG_SIZ, "time %ld\n", time);
14422 SendToProgram(message, cps);
14424 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14425 SendToProgram(message, cps);
14429 BoolFeature(p, name, loc, cps)
14433 ChessProgramState *cps;
14436 int len = strlen(name);
14439 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14441 sscanf(*p, "%d", &val);
14443 while (**p && **p != ' ')
14445 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14446 SendToProgram(buf, cps);
14453 IntFeature(p, name, loc, cps)
14457 ChessProgramState *cps;
14460 int len = strlen(name);
14461 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14463 sscanf(*p, "%d", loc);
14464 while (**p && **p != ' ') (*p)++;
14465 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14466 SendToProgram(buf, cps);
14473 StringFeature(p, name, loc, cps)
14477 ChessProgramState *cps;
14480 int len = strlen(name);
14481 if (strncmp((*p), name, len) == 0
14482 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14484 sscanf(*p, "%[^\"]", loc);
14485 while (**p && **p != '\"') (*p)++;
14486 if (**p == '\"') (*p)++;
14487 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488 SendToProgram(buf, cps);
14495 ParseOption(Option *opt, ChessProgramState *cps)
14496 // [HGM] options: process the string that defines an engine option, and determine
14497 // name, type, default value, and allowed value range
14499 char *p, *q, buf[MSG_SIZ];
14500 int n, min = (-1)<<31, max = 1<<31, def;
14502 if(p = strstr(opt->name, " -spin ")) {
14503 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14504 if(max < min) max = min; // enforce consistency
14505 if(def < min) def = min;
14506 if(def > max) def = max;
14511 } else if((p = strstr(opt->name, " -slider "))) {
14512 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14513 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14514 if(max < min) max = min; // enforce consistency
14515 if(def < min) def = min;
14516 if(def > max) def = max;
14520 opt->type = Spin; // Slider;
14521 } else if((p = strstr(opt->name, " -string "))) {
14522 opt->textValue = p+9;
14523 opt->type = TextBox;
14524 } else if((p = strstr(opt->name, " -file "))) {
14525 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14526 opt->textValue = p+7;
14527 opt->type = FileName; // FileName;
14528 } else if((p = strstr(opt->name, " -path "))) {
14529 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14530 opt->textValue = p+7;
14531 opt->type = PathName; // PathName;
14532 } else if(p = strstr(opt->name, " -check ")) {
14533 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14534 opt->value = (def != 0);
14535 opt->type = CheckBox;
14536 } else if(p = strstr(opt->name, " -combo ")) {
14537 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14538 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14539 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14540 opt->value = n = 0;
14541 while(q = StrStr(q, " /// ")) {
14542 n++; *q = 0; // count choices, and null-terminate each of them
14544 if(*q == '*') { // remember default, which is marked with * prefix
14548 cps->comboList[cps->comboCnt++] = q;
14550 cps->comboList[cps->comboCnt++] = NULL;
14552 opt->type = ComboBox;
14553 } else if(p = strstr(opt->name, " -button")) {
14554 opt->type = Button;
14555 } else if(p = strstr(opt->name, " -save")) {
14556 opt->type = SaveButton;
14557 } else return FALSE;
14558 *p = 0; // terminate option name
14559 // now look if the command-line options define a setting for this engine option.
14560 if(cps->optionSettings && cps->optionSettings[0])
14561 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14562 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14563 snprintf(buf, MSG_SIZ, "option %s", p);
14564 if(p = strstr(buf, ",")) *p = 0;
14565 if(q = strchr(buf, '=')) switch(opt->type) {
14567 for(n=0; n<opt->max; n++)
14568 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14571 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14575 opt->value = atoi(q+1);
14580 SendToProgram(buf, cps);
14586 FeatureDone(cps, val)
14587 ChessProgramState* cps;
14590 DelayedEventCallback cb = GetDelayedEvent();
14591 if ((cb == InitBackEnd3 && cps == &first) ||
14592 (cb == SettingsMenuIfReady && cps == &second) ||
14593 (cb == LoadEngine) ||
14594 (cb == TwoMachinesEventIfReady)) {
14595 CancelDelayedEvent();
14596 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14598 cps->initDone = val;
14601 /* Parse feature command from engine */
14603 ParseFeatures(args, cps)
14605 ChessProgramState *cps;
14613 while (*p == ' ') p++;
14614 if (*p == NULLCHAR) return;
14616 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14617 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14618 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14619 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14620 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14621 if (BoolFeature(&p, "reuse", &val, cps)) {
14622 /* Engine can disable reuse, but can't enable it if user said no */
14623 if (!val) cps->reuse = FALSE;
14626 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14627 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14628 if (gameMode == TwoMachinesPlay) {
14629 DisplayTwoMachinesTitle();
14635 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14636 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14637 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14638 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14639 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14640 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14641 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14642 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14643 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14644 if (IntFeature(&p, "done", &val, cps)) {
14645 FeatureDone(cps, val);
14648 /* Added by Tord: */
14649 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14650 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14651 /* End of additions by Tord */
14653 /* [HGM] added features: */
14654 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14655 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14656 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14657 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14658 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14659 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14660 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14661 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14662 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14663 SendToProgram(buf, cps);
14666 if(cps->nrOptions >= MAX_OPTIONS) {
14668 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14669 DisplayError(buf, 0);
14673 /* End of additions by HGM */
14675 /* unknown feature: complain and skip */
14677 while (*q && *q != '=') q++;
14678 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14679 SendToProgram(buf, cps);
14685 while (*p && *p != '\"') p++;
14686 if (*p == '\"') p++;
14688 while (*p && *p != ' ') p++;
14696 PeriodicUpdatesEvent(newState)
14699 if (newState == appData.periodicUpdates)
14702 appData.periodicUpdates=newState;
14704 /* Display type changes, so update it now */
14705 // DisplayAnalysis();
14707 /* Get the ball rolling again... */
14709 AnalysisPeriodicEvent(1);
14710 StartAnalysisClock();
14715 PonderNextMoveEvent(newState)
14718 if (newState == appData.ponderNextMove) return;
14719 if (gameMode == EditPosition) EditPositionDone(TRUE);
14721 SendToProgram("hard\n", &first);
14722 if (gameMode == TwoMachinesPlay) {
14723 SendToProgram("hard\n", &second);
14726 SendToProgram("easy\n", &first);
14727 thinkOutput[0] = NULLCHAR;
14728 if (gameMode == TwoMachinesPlay) {
14729 SendToProgram("easy\n", &second);
14732 appData.ponderNextMove = newState;
14736 NewSettingEvent(option, feature, command, value)
14738 int option, value, *feature;
14742 if (gameMode == EditPosition) EditPositionDone(TRUE);
14743 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14744 if(feature == NULL || *feature) SendToProgram(buf, &first);
14745 if (gameMode == TwoMachinesPlay) {
14746 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14751 ShowThinkingEvent()
14752 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14754 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14755 int newState = appData.showThinking
14756 // [HGM] thinking: other features now need thinking output as well
14757 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14759 if (oldState == newState) return;
14760 oldState = newState;
14761 if (gameMode == EditPosition) EditPositionDone(TRUE);
14763 SendToProgram("post\n", &first);
14764 if (gameMode == TwoMachinesPlay) {
14765 SendToProgram("post\n", &second);
14768 SendToProgram("nopost\n", &first);
14769 thinkOutput[0] = NULLCHAR;
14770 if (gameMode == TwoMachinesPlay) {
14771 SendToProgram("nopost\n", &second);
14774 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14778 AskQuestionEvent(title, question, replyPrefix, which)
14779 char *title; char *question; char *replyPrefix; char *which;
14781 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14782 if (pr == NoProc) return;
14783 AskQuestion(title, question, replyPrefix, pr);
14787 TypeInEvent(char firstChar)
14789 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14790 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14791 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14792 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14793 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14794 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14795 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14796 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14797 gameMode == Training) PopUpMoveDialog(firstChar);
14801 TypeInDoneEvent(char *move)
14804 int n, fromX, fromY, toX, toY;
14806 ChessMove moveType;
\r
14809 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14810 EditPositionPasteFEN(move);
\r
14813 // [HGM] movenum: allow move number to be typed in any mode
\r
14814 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14815 ToNrEvent(2*n-1);
\r
14819 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14820 gameMode != Training) {
\r
14821 DisplayMoveError(_("Displayed move is not current"));
\r
14823 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14824 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14825 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14826 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14827 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14828 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14830 DisplayMoveError(_("Could not parse move"));
\r
14836 DisplayMove(moveNumber)
14839 char message[MSG_SIZ];
14841 char cpThinkOutput[MSG_SIZ];
14843 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14845 if (moveNumber == forwardMostMove - 1 ||
14846 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14848 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14850 if (strchr(cpThinkOutput, '\n')) {
14851 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14854 *cpThinkOutput = NULLCHAR;
14857 /* [AS] Hide thinking from human user */
14858 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14859 *cpThinkOutput = NULLCHAR;
14860 if( thinkOutput[0] != NULLCHAR ) {
14863 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14864 cpThinkOutput[i] = '.';
14866 cpThinkOutput[i] = NULLCHAR;
14867 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14871 if (moveNumber == forwardMostMove - 1 &&
14872 gameInfo.resultDetails != NULL) {
14873 if (gameInfo.resultDetails[0] == NULLCHAR) {
14874 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14876 snprintf(res, MSG_SIZ, " {%s} %s",
14877 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14883 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14884 DisplayMessage(res, cpThinkOutput);
14886 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14887 WhiteOnMove(moveNumber) ? " " : ".. ",
14888 parseList[moveNumber], res);
14889 DisplayMessage(message, cpThinkOutput);
14894 DisplayComment(moveNumber, text)
14898 char title[MSG_SIZ];
14899 char buf[8000]; // comment can be long!
14902 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14903 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14905 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14906 WhiteOnMove(moveNumber) ? " " : ".. ",
14907 parseList[moveNumber]);
14909 // [HGM] PV info: display PV info together with (or as) comment
14910 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14911 if(text == NULL) text = "";
14912 score = pvInfoList[moveNumber].score;
14913 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14914 depth, (pvInfoList[moveNumber].time+50)/100, text);
14917 if (text != NULL && (appData.autoDisplayComment || commentUp))
14918 CommentPopUp(title, text);
14921 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14922 * might be busy thinking or pondering. It can be omitted if your
14923 * gnuchess is configured to stop thinking immediately on any user
14924 * input. However, that gnuchess feature depends on the FIONREAD
14925 * ioctl, which does not work properly on some flavors of Unix.
14929 ChessProgramState *cps;
14932 if (!cps->useSigint) return;
14933 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14934 switch (gameMode) {
14935 case MachinePlaysWhite:
14936 case MachinePlaysBlack:
14937 case TwoMachinesPlay:
14938 case IcsPlayingWhite:
14939 case IcsPlayingBlack:
14942 /* Skip if we know it isn't thinking */
14943 if (!cps->maybeThinking) return;
14944 if (appData.debugMode)
14945 fprintf(debugFP, "Interrupting %s\n", cps->which);
14946 InterruptChildProcess(cps->pr);
14947 cps->maybeThinking = FALSE;
14952 #endif /*ATTENTION*/
14958 if (whiteTimeRemaining <= 0) {
14961 if (appData.icsActive) {
14962 if (appData.autoCallFlag &&
14963 gameMode == IcsPlayingBlack && !blackFlag) {
14964 SendToICS(ics_prefix);
14965 SendToICS("flag\n");
14969 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14971 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14972 if (appData.autoCallFlag) {
14973 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14980 if (blackTimeRemaining <= 0) {
14983 if (appData.icsActive) {
14984 if (appData.autoCallFlag &&
14985 gameMode == IcsPlayingWhite && !whiteFlag) {
14986 SendToICS(ics_prefix);
14987 SendToICS("flag\n");
14991 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14993 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14994 if (appData.autoCallFlag) {
14995 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15008 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15009 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15012 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15014 if ( !WhiteOnMove(forwardMostMove) ) {
15015 /* White made time control */
15016 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15017 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15018 /* [HGM] time odds: correct new time quota for time odds! */
15019 / WhitePlayer()->timeOdds;
15020 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15022 lastBlack -= blackTimeRemaining;
15023 /* Black made time control */
15024 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15025 / WhitePlayer()->other->timeOdds;
15026 lastWhite = whiteTimeRemaining;
15031 DisplayBothClocks()
15033 int wom = gameMode == EditPosition ?
15034 !blackPlaysFirst : WhiteOnMove(currentMove);
15035 DisplayWhiteClock(whiteTimeRemaining, wom);
15036 DisplayBlackClock(blackTimeRemaining, !wom);
15040 /* Timekeeping seems to be a portability nightmare. I think everyone
15041 has ftime(), but I'm really not sure, so I'm including some ifdefs
15042 to use other calls if you don't. Clocks will be less accurate if
15043 you have neither ftime nor gettimeofday.
15046 /* VS 2008 requires the #include outside of the function */
15047 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15048 #include <sys/timeb.h>
15051 /* Get the current time as a TimeMark */
15056 #if HAVE_GETTIMEOFDAY
15058 struct timeval timeVal;
15059 struct timezone timeZone;
15061 gettimeofday(&timeVal, &timeZone);
15062 tm->sec = (long) timeVal.tv_sec;
15063 tm->ms = (int) (timeVal.tv_usec / 1000L);
15065 #else /*!HAVE_GETTIMEOFDAY*/
15068 // include <sys/timeb.h> / moved to just above start of function
15069 struct timeb timeB;
15072 tm->sec = (long) timeB.time;
15073 tm->ms = (int) timeB.millitm;
15075 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15076 tm->sec = (long) time(NULL);
15082 /* Return the difference in milliseconds between two
15083 time marks. We assume the difference will fit in a long!
15086 SubtractTimeMarks(tm2, tm1)
15087 TimeMark *tm2, *tm1;
15089 return 1000L*(tm2->sec - tm1->sec) +
15090 (long) (tm2->ms - tm1->ms);
15095 * Code to manage the game clocks.
15097 * In tournament play, black starts the clock and then white makes a move.
15098 * We give the human user a slight advantage if he is playing white---the
15099 * clocks don't run until he makes his first move, so it takes zero time.
15100 * Also, we don't account for network lag, so we could get out of sync
15101 * with GNU Chess's clock -- but then, referees are always right.
15104 static TimeMark tickStartTM;
15105 static long intendedTickLength;
15108 NextTickLength(timeRemaining)
15109 long timeRemaining;
15111 long nominalTickLength, nextTickLength;
15113 if (timeRemaining > 0L && timeRemaining <= 10000L)
15114 nominalTickLength = 100L;
15116 nominalTickLength = 1000L;
15117 nextTickLength = timeRemaining % nominalTickLength;
15118 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15120 return nextTickLength;
15123 /* Adjust clock one minute up or down */
15125 AdjustClock(Boolean which, int dir)
15127 if(which) blackTimeRemaining += 60000*dir;
15128 else whiteTimeRemaining += 60000*dir;
15129 DisplayBothClocks();
15132 /* Stop clocks and reset to a fresh time control */
15136 (void) StopClockTimer();
15137 if (appData.icsActive) {
15138 whiteTimeRemaining = blackTimeRemaining = 0;
15139 } else if (searchTime) {
15140 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15141 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15142 } else { /* [HGM] correct new time quote for time odds */
15143 whiteTC = blackTC = fullTimeControlString;
15144 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15145 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15147 if (whiteFlag || blackFlag) {
15149 whiteFlag = blackFlag = FALSE;
15151 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15152 DisplayBothClocks();
15155 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15157 /* Decrement running clock by amount of time that has passed */
15161 long timeRemaining;
15162 long lastTickLength, fudge;
15165 if (!appData.clockMode) return;
15166 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15170 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15172 /* Fudge if we woke up a little too soon */
15173 fudge = intendedTickLength - lastTickLength;
15174 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15176 if (WhiteOnMove(forwardMostMove)) {
15177 if(whiteNPS >= 0) lastTickLength = 0;
15178 timeRemaining = whiteTimeRemaining -= lastTickLength;
15179 if(timeRemaining < 0 && !appData.icsActive) {
15180 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15181 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15182 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15183 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15186 DisplayWhiteClock(whiteTimeRemaining - fudge,
15187 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15189 if(blackNPS >= 0) lastTickLength = 0;
15190 timeRemaining = blackTimeRemaining -= lastTickLength;
15191 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15192 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15194 blackStartMove = forwardMostMove;
15195 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15198 DisplayBlackClock(blackTimeRemaining - fudge,
15199 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15201 if (CheckFlags()) return;
15204 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15205 StartClockTimer(intendedTickLength);
15207 /* if the time remaining has fallen below the alarm threshold, sound the
15208 * alarm. if the alarm has sounded and (due to a takeback or time control
15209 * with increment) the time remaining has increased to a level above the
15210 * threshold, reset the alarm so it can sound again.
15213 if (appData.icsActive && appData.icsAlarm) {
15215 /* make sure we are dealing with the user's clock */
15216 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15217 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15220 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15221 alarmSounded = FALSE;
15222 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15224 alarmSounded = TRUE;
15230 /* A player has just moved, so stop the previously running
15231 clock and (if in clock mode) start the other one.
15232 We redisplay both clocks in case we're in ICS mode, because
15233 ICS gives us an update to both clocks after every move.
15234 Note that this routine is called *after* forwardMostMove
15235 is updated, so the last fractional tick must be subtracted
15236 from the color that is *not* on move now.
15239 SwitchClocks(int newMoveNr)
15241 long lastTickLength;
15243 int flagged = FALSE;
15247 if (StopClockTimer() && appData.clockMode) {
15248 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15249 if (!WhiteOnMove(forwardMostMove)) {
15250 if(blackNPS >= 0) lastTickLength = 0;
15251 blackTimeRemaining -= lastTickLength;
15252 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15253 // if(pvInfoList[forwardMostMove].time == -1)
15254 pvInfoList[forwardMostMove].time = // use GUI time
15255 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15257 if(whiteNPS >= 0) lastTickLength = 0;
15258 whiteTimeRemaining -= lastTickLength;
15259 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15260 // if(pvInfoList[forwardMostMove].time == -1)
15261 pvInfoList[forwardMostMove].time =
15262 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15264 flagged = CheckFlags();
15266 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15267 CheckTimeControl();
15269 if (flagged || !appData.clockMode) return;
15271 switch (gameMode) {
15272 case MachinePlaysBlack:
15273 case MachinePlaysWhite:
15274 case BeginningOfGame:
15275 if (pausing) return;
15279 case PlayFromGameFile:
15287 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15288 if(WhiteOnMove(forwardMostMove))
15289 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15290 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15294 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15295 whiteTimeRemaining : blackTimeRemaining);
15296 StartClockTimer(intendedTickLength);
15300 /* Stop both clocks */
15304 long lastTickLength;
15307 if (!StopClockTimer()) return;
15308 if (!appData.clockMode) return;
15312 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15313 if (WhiteOnMove(forwardMostMove)) {
15314 if(whiteNPS >= 0) lastTickLength = 0;
15315 whiteTimeRemaining -= lastTickLength;
15316 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15318 if(blackNPS >= 0) lastTickLength = 0;
15319 blackTimeRemaining -= lastTickLength;
15320 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15325 /* Start clock of player on move. Time may have been reset, so
15326 if clock is already running, stop and restart it. */
15330 (void) StopClockTimer(); /* in case it was running already */
15331 DisplayBothClocks();
15332 if (CheckFlags()) return;
15334 if (!appData.clockMode) return;
15335 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15337 GetTimeMark(&tickStartTM);
15338 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15339 whiteTimeRemaining : blackTimeRemaining);
15341 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15342 whiteNPS = blackNPS = -1;
15343 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15344 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15345 whiteNPS = first.nps;
15346 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15347 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15348 blackNPS = first.nps;
15349 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15350 whiteNPS = second.nps;
15351 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15352 blackNPS = second.nps;
15353 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15355 StartClockTimer(intendedTickLength);
15362 long second, minute, hour, day;
15364 static char buf[32];
15366 if (ms > 0 && ms <= 9900) {
15367 /* convert milliseconds to tenths, rounding up */
15368 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15370 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15374 /* convert milliseconds to seconds, rounding up */
15375 /* use floating point to avoid strangeness of integer division
15376 with negative dividends on many machines */
15377 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15384 day = second / (60 * 60 * 24);
15385 second = second % (60 * 60 * 24);
15386 hour = second / (60 * 60);
15387 second = second % (60 * 60);
15388 minute = second / 60;
15389 second = second % 60;
15392 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15393 sign, day, hour, minute, second);
15395 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15397 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15404 * This is necessary because some C libraries aren't ANSI C compliant yet.
15407 StrStr(string, match)
15408 char *string, *match;
15412 length = strlen(match);
15414 for (i = strlen(string) - length; i >= 0; i--, string++)
15415 if (!strncmp(match, string, length))
15422 StrCaseStr(string, match)
15423 char *string, *match;
15427 length = strlen(match);
15429 for (i = strlen(string) - length; i >= 0; i--, string++) {
15430 for (j = 0; j < length; j++) {
15431 if (ToLower(match[j]) != ToLower(string[j]))
15434 if (j == length) return string;
15448 c1 = ToLower(*s1++);
15449 c2 = ToLower(*s2++);
15450 if (c1 > c2) return 1;
15451 if (c1 < c2) return -1;
15452 if (c1 == NULLCHAR) return 0;
15461 return isupper(c) ? tolower(c) : c;
15469 return islower(c) ? toupper(c) : c;
15471 #endif /* !_amigados */
15479 if ((ret = (char *) malloc(strlen(s) + 1)))
15481 safeStrCpy(ret, s, strlen(s)+1);
15487 StrSavePtr(s, savePtr)
15488 char *s, **savePtr;
15493 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15494 safeStrCpy(*savePtr, s, strlen(s)+1);
15506 clock = time((time_t *)NULL);
15507 tm = localtime(&clock);
15508 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15509 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15510 return StrSave(buf);
15515 PositionToFEN(move, overrideCastling)
15517 char *overrideCastling;
15519 int i, j, fromX, fromY, toX, toY;
15526 whiteToPlay = (gameMode == EditPosition) ?
15527 !blackPlaysFirst : (move % 2 == 0);
15530 /* Piece placement data */
15531 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15533 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15534 if (boards[move][i][j] == EmptySquare) {
15536 } else { ChessSquare piece = boards[move][i][j];
15537 if (emptycount > 0) {
15538 if(emptycount<10) /* [HGM] can be >= 10 */
15539 *p++ = '0' + emptycount;
15540 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15543 if(PieceToChar(piece) == '+') {
15544 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15546 piece = (ChessSquare)(DEMOTED piece);
15548 *p++ = PieceToChar(piece);
15550 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15551 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15556 if (emptycount > 0) {
15557 if(emptycount<10) /* [HGM] can be >= 10 */
15558 *p++ = '0' + emptycount;
15559 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15566 /* [HGM] print Crazyhouse or Shogi holdings */
15567 if( gameInfo.holdingsWidth ) {
15568 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15570 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15571 piece = boards[move][i][BOARD_WIDTH-1];
15572 if( piece != EmptySquare )
15573 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15574 *p++ = PieceToChar(piece);
15576 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15577 piece = boards[move][BOARD_HEIGHT-i-1][0];
15578 if( piece != EmptySquare )
15579 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15580 *p++ = PieceToChar(piece);
15583 if( q == p ) *p++ = '-';
15589 *p++ = whiteToPlay ? 'w' : 'b';
15592 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15593 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15595 if(nrCastlingRights) {
15597 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15598 /* [HGM] write directly from rights */
15599 if(boards[move][CASTLING][2] != NoRights &&
15600 boards[move][CASTLING][0] != NoRights )
15601 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15602 if(boards[move][CASTLING][2] != NoRights &&
15603 boards[move][CASTLING][1] != NoRights )
15604 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15605 if(boards[move][CASTLING][5] != NoRights &&
15606 boards[move][CASTLING][3] != NoRights )
15607 *p++ = boards[move][CASTLING][3] + AAA;
15608 if(boards[move][CASTLING][5] != NoRights &&
15609 boards[move][CASTLING][4] != NoRights )
15610 *p++ = boards[move][CASTLING][4] + AAA;
15613 /* [HGM] write true castling rights */
15614 if( nrCastlingRights == 6 ) {
15615 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15616 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15617 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15618 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15619 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15620 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15621 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15622 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15625 if (q == p) *p++ = '-'; /* No castling rights */
15629 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15630 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15631 /* En passant target square */
15632 if (move > backwardMostMove) {
15633 fromX = moveList[move - 1][0] - AAA;
15634 fromY = moveList[move - 1][1] - ONE;
15635 toX = moveList[move - 1][2] - AAA;
15636 toY = moveList[move - 1][3] - ONE;
15637 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15638 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15639 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15641 /* 2-square pawn move just happened */
15643 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15647 } else if(move == backwardMostMove) {
15648 // [HGM] perhaps we should always do it like this, and forget the above?
15649 if((signed char)boards[move][EP_STATUS] >= 0) {
15650 *p++ = boards[move][EP_STATUS] + AAA;
15651 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15662 /* [HGM] find reversible plies */
15663 { int i = 0, j=move;
15665 if (appData.debugMode) { int k;
15666 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15667 for(k=backwardMostMove; k<=forwardMostMove; k++)
15668 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15672 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15673 if( j == backwardMostMove ) i += initialRulePlies;
15674 sprintf(p, "%d ", i);
15675 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15677 /* Fullmove number */
15678 sprintf(p, "%d", (move / 2) + 1);
15680 return StrSave(buf);
15684 ParseFEN(board, blackPlaysFirst, fen)
15686 int *blackPlaysFirst;
15696 /* [HGM] by default clear Crazyhouse holdings, if present */
15697 if(gameInfo.holdingsWidth) {
15698 for(i=0; i<BOARD_HEIGHT; i++) {
15699 board[i][0] = EmptySquare; /* black holdings */
15700 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15701 board[i][1] = (ChessSquare) 0; /* black counts */
15702 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15706 /* Piece placement data */
15707 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15710 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15711 if (*p == '/') p++;
15712 emptycount = gameInfo.boardWidth - j;
15713 while (emptycount--)
15714 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15716 #if(BOARD_FILES >= 10)
15717 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15718 p++; emptycount=10;
15719 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15720 while (emptycount--)
15721 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15723 } else if (isdigit(*p)) {
15724 emptycount = *p++ - '0';
15725 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15726 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15727 while (emptycount--)
15728 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15729 } else if (*p == '+' || isalpha(*p)) {
15730 if (j >= gameInfo.boardWidth) return FALSE;
15732 piece = CharToPiece(*++p);
15733 if(piece == EmptySquare) return FALSE; /* unknown piece */
15734 piece = (ChessSquare) (PROMOTED piece ); p++;
15735 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15736 } else piece = CharToPiece(*p++);
15738 if(piece==EmptySquare) return FALSE; /* unknown piece */
15739 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15740 piece = (ChessSquare) (PROMOTED piece);
15741 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15744 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15750 while (*p == '/' || *p == ' ') p++;
15752 /* [HGM] look for Crazyhouse holdings here */
15753 while(*p==' ') p++;
15754 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15756 if(*p == '-' ) p++; /* empty holdings */ else {
15757 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15758 /* if we would allow FEN reading to set board size, we would */
15759 /* have to add holdings and shift the board read so far here */
15760 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15762 if((int) piece >= (int) BlackPawn ) {
15763 i = (int)piece - (int)BlackPawn;
15764 i = PieceToNumber((ChessSquare)i);
15765 if( i >= gameInfo.holdingsSize ) return FALSE;
15766 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15767 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15769 i = (int)piece - (int)WhitePawn;
15770 i = PieceToNumber((ChessSquare)i);
15771 if( i >= gameInfo.holdingsSize ) return FALSE;
15772 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15773 board[i][BOARD_WIDTH-2]++; /* black holdings */
15780 while(*p == ' ') p++;
15784 if(appData.colorNickNames) {
15785 if( c == appData.colorNickNames[0] ) c = 'w'; else
15786 if( c == appData.colorNickNames[1] ) c = 'b';
15790 *blackPlaysFirst = FALSE;
15793 *blackPlaysFirst = TRUE;
15799 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15800 /* return the extra info in global variiables */
15802 /* set defaults in case FEN is incomplete */
15803 board[EP_STATUS] = EP_UNKNOWN;
15804 for(i=0; i<nrCastlingRights; i++ ) {
15805 board[CASTLING][i] =
15806 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15807 } /* assume possible unless obviously impossible */
15808 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15809 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15810 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15811 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15812 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15813 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15814 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15815 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15818 while(*p==' ') p++;
15819 if(nrCastlingRights) {
15820 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15821 /* castling indicator present, so default becomes no castlings */
15822 for(i=0; i<nrCastlingRights; i++ ) {
15823 board[CASTLING][i] = NoRights;
15826 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15827 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15828 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15829 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15830 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15832 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15833 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15834 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15836 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15837 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15838 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15839 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15840 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15841 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15844 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15845 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15846 board[CASTLING][2] = whiteKingFile;
15849 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15850 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15851 board[CASTLING][2] = whiteKingFile;
15854 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15855 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15856 board[CASTLING][5] = blackKingFile;
15859 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15860 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15861 board[CASTLING][5] = blackKingFile;
15864 default: /* FRC castlings */
15865 if(c >= 'a') { /* black rights */
15866 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15867 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15868 if(i == BOARD_RGHT) break;
15869 board[CASTLING][5] = i;
15871 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15872 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15874 board[CASTLING][3] = c;
15876 board[CASTLING][4] = c;
15877 } else { /* white rights */
15878 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15879 if(board[0][i] == WhiteKing) break;
15880 if(i == BOARD_RGHT) break;
15881 board[CASTLING][2] = i;
15882 c -= AAA - 'a' + 'A';
15883 if(board[0][c] >= WhiteKing) break;
15885 board[CASTLING][0] = c;
15887 board[CASTLING][1] = c;
15891 for(i=0; i<nrCastlingRights; i++)
15892 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15893 if (appData.debugMode) {
15894 fprintf(debugFP, "FEN castling rights:");
15895 for(i=0; i<nrCastlingRights; i++)
15896 fprintf(debugFP, " %d", board[CASTLING][i]);
15897 fprintf(debugFP, "\n");
15900 while(*p==' ') p++;
15903 /* read e.p. field in games that know e.p. capture */
15904 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15905 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15907 p++; board[EP_STATUS] = EP_NONE;
15909 char c = *p++ - AAA;
15911 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15912 if(*p >= '0' && *p <='9') p++;
15913 board[EP_STATUS] = c;
15918 if(sscanf(p, "%d", &i) == 1) {
15919 FENrulePlies = i; /* 50-move ply counter */
15920 /* (The move number is still ignored) */
15927 EditPositionPasteFEN(char *fen)
15930 Board initial_position;
15932 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15933 DisplayError(_("Bad FEN position in clipboard"), 0);
15936 int savedBlackPlaysFirst = blackPlaysFirst;
15937 EditPositionEvent();
15938 blackPlaysFirst = savedBlackPlaysFirst;
15939 CopyBoard(boards[0], initial_position);
15940 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15941 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15942 DisplayBothClocks();
15943 DrawPosition(FALSE, boards[currentMove]);
15948 static char cseq[12] = "\\ ";
15950 Boolean set_cont_sequence(char *new_seq)
15955 // handle bad attempts to set the sequence
15957 return 0; // acceptable error - no debug
15959 len = strlen(new_seq);
15960 ret = (len > 0) && (len < sizeof(cseq));
15962 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15963 else if (appData.debugMode)
15964 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15969 reformat a source message so words don't cross the width boundary. internal
15970 newlines are not removed. returns the wrapped size (no null character unless
15971 included in source message). If dest is NULL, only calculate the size required
15972 for the dest buffer. lp argument indicats line position upon entry, and it's
15973 passed back upon exit.
15975 int wrap(char *dest, char *src, int count, int width, int *lp)
15977 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15979 cseq_len = strlen(cseq);
15980 old_line = line = *lp;
15981 ansi = len = clen = 0;
15983 for (i=0; i < count; i++)
15985 if (src[i] == '\033')
15988 // if we hit the width, back up
15989 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15991 // store i & len in case the word is too long
15992 old_i = i, old_len = len;
15994 // find the end of the last word
15995 while (i && src[i] != ' ' && src[i] != '\n')
16001 // word too long? restore i & len before splitting it
16002 if ((old_i-i+clen) >= width)
16009 if (i && src[i-1] == ' ')
16012 if (src[i] != ' ' && src[i] != '\n')
16019 // now append the newline and continuation sequence
16024 strncpy(dest+len, cseq, cseq_len);
16032 dest[len] = src[i];
16036 if (src[i] == '\n')
16041 if (dest && appData.debugMode)
16043 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16044 count, width, line, len, *lp);
16045 show_bytes(debugFP, src, count);
16046 fprintf(debugFP, "\ndest: ");
16047 show_bytes(debugFP, dest, len);
16048 fprintf(debugFP, "\n");
16050 *lp = dest ? line : old_line;
16055 // [HGM] vari: routines for shelving variations
16058 PushTail(int firstMove, int lastMove)
16060 int i, j, nrMoves = lastMove - firstMove;
16062 if(appData.icsActive) { // only in local mode
16063 forwardMostMove = currentMove; // mimic old ICS behavior
16066 if(storedGames >= MAX_VARIATIONS-1) return;
16068 // push current tail of game on stack
16069 savedResult[storedGames] = gameInfo.result;
16070 savedDetails[storedGames] = gameInfo.resultDetails;
16071 gameInfo.resultDetails = NULL;
16072 savedFirst[storedGames] = firstMove;
16073 savedLast [storedGames] = lastMove;
16074 savedFramePtr[storedGames] = framePtr;
16075 framePtr -= nrMoves; // reserve space for the boards
16076 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16077 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16078 for(j=0; j<MOVE_LEN; j++)
16079 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16080 for(j=0; j<2*MOVE_LEN; j++)
16081 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16082 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16083 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16084 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16085 pvInfoList[firstMove+i-1].depth = 0;
16086 commentList[framePtr+i] = commentList[firstMove+i];
16087 commentList[firstMove+i] = NULL;
16091 forwardMostMove = firstMove; // truncate game so we can start variation
16092 if(storedGames == 1) GreyRevert(FALSE);
16096 PopTail(Boolean annotate)
16099 char buf[8000], moveBuf[20];
16101 if(appData.icsActive) return FALSE; // only in local mode
16102 if(!storedGames) return FALSE; // sanity
16103 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16106 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16107 nrMoves = savedLast[storedGames] - currentMove;
16110 if(!WhiteOnMove(currentMove))
16111 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16112 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16113 for(i=currentMove; i<forwardMostMove; i++) {
16115 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16116 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16117 strcat(buf, moveBuf);
16118 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16119 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16123 for(i=1; i<=nrMoves; i++) { // copy last variation back
16124 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16125 for(j=0; j<MOVE_LEN; j++)
16126 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16127 for(j=0; j<2*MOVE_LEN; j++)
16128 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16129 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16130 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16131 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16132 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16133 commentList[currentMove+i] = commentList[framePtr+i];
16134 commentList[framePtr+i] = NULL;
16136 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16137 framePtr = savedFramePtr[storedGames];
16138 gameInfo.result = savedResult[storedGames];
16139 if(gameInfo.resultDetails != NULL) {
16140 free(gameInfo.resultDetails);
16142 gameInfo.resultDetails = savedDetails[storedGames];
16143 forwardMostMove = currentMove + nrMoves;
16144 if(storedGames == 0) GreyRevert(TRUE);
16150 { // remove all shelved variations
16152 for(i=0; i<storedGames; i++) {
16153 if(savedDetails[i])
16154 free(savedDetails[i]);
16155 savedDetails[i] = NULL;
16157 for(i=framePtr; i<MAX_MOVES; i++) {
16158 if(commentList[i]) free(commentList[i]);
16159 commentList[i] = NULL;
16161 framePtr = MAX_MOVES-1;
16166 LoadVariation(int index, char *text)
16167 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16168 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16169 int level = 0, move;
16171 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16172 // first find outermost bracketing variation
16173 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16174 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16175 if(*p == '{') wait = '}'; else
16176 if(*p == '[') wait = ']'; else
16177 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16178 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16180 if(*p == wait) wait = NULLCHAR; // closing ]} found
16183 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16184 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16185 end[1] = NULLCHAR; // clip off comment beyond variation
16186 ToNrEvent(currentMove-1);
16187 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16188 // kludge: use ParsePV() to append variation to game
16189 move = currentMove;
16190 ParsePV(start, TRUE);
16191 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16192 ClearPremoveHighlights();
16194 ToNrEvent(currentMove+1);