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));
236 FILE *WriteTourneyFile P((char *results));
239 extern void ConsoleCreate();
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
262 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
285 /* States for ics_getting_history */
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
293 /* whosays values for GameEnds */
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
305 /* Different types of move when calling RegisterMove */
307 #define CMAIL_RESIGN 1
309 #define CMAIL_ACCEPT 3
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
316 /* Telnet protocol constants */
327 safeStrCpy( char *dst, const char *src, size_t count )
330 assert( dst != NULL );
331 assert( src != NULL );
334 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335 if( i == count && dst[count-1] != NULLCHAR)
337 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338 if(appData.debugMode)
339 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
345 /* Some compiler can't cast u64 to double
346 * This function do the job for us:
348 * We use the highest bit for cast, this only
349 * works if the highest bit is not
350 * in use (This should not happen)
352 * We used this for all compiler
355 u64ToDouble(u64 value)
358 u64 tmp = value & u64Const(0x7fffffffffffffff);
359 r = (double)(s64)tmp;
360 if (value & u64Const(0x8000000000000000))
361 r += 9.2233720368547758080e18; /* 2^63 */
365 /* Fake up flags for now, as we aren't keeping track of castling
366 availability yet. [HGM] Change of logic: the flag now only
367 indicates the type of castlings allowed by the rule of the game.
368 The actual rights themselves are maintained in the array
369 castlingRights, as part of the game history, and are not probed
375 int flags = F_ALL_CASTLE_OK;
376 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377 switch (gameInfo.variant) {
379 flags &= ~F_ALL_CASTLE_OK;
380 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381 flags |= F_IGNORE_CHECK;
383 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388 case VariantKriegspiel:
389 flags |= F_KRIEGSPIEL_CAPTURE;
391 case VariantCapaRandom:
392 case VariantFischeRandom:
393 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394 case VariantNoCastle:
395 case VariantShatranj:
398 flags &= ~F_ALL_CASTLE_OK;
406 FILE *gameFileFP, *debugFP;
409 [AS] Note: sometimes, the sscanf() function is used to parse the input
410 into a fixed-size buffer. Because of this, we must be prepared to
411 receive strings as long as the size of the input buffer, which is currently
412 set to 4K for Windows and 8K for the rest.
413 So, we must either allocate sufficiently large buffers here, or
414 reduce the size of the input buffer in the input reading part.
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
421 ChessProgramState first, second;
423 /* premove variables */
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
464 int have_sent_ICS_logon = 0;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
477 /* animateTraining preserves the state of appData.animate
478 * when Training mode is activated. This allows the
479 * response to be animated when appData.animate == TRUE and
480 * appData.animateDragging == TRUE.
482 Boolean animateTraining;
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char initialRights[BOARD_FILES];
492 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int initialRulePlies, FENrulePlies;
494 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int mute; // mute all sounds
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
515 ChessSquare FIDEArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackBishop, BlackKnight, BlackRook }
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526 BlackKing, BlackKing, BlackKnight, BlackRook }
529 ChessSquare KnightmateArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532 { BlackRook, BlackMan, BlackBishop, BlackQueen,
533 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackMan, BlackFerz,
561 BlackKing, BlackMan, BlackKnight, BlackRook }
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating(str)
652 while(*str && !isdigit(*str)) ++str;
654 return 0; /* One of the special "no rating" cases */
662 /* Init programStats */
663 programStats.movelist[0] = 0;
664 programStats.depth = 0;
665 programStats.nr_moves = 0;
666 programStats.moves_left = 0;
667 programStats.nodes = 0;
668 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
669 programStats.score = 0;
670 programStats.got_only_move = 0;
671 programStats.got_fail = 0;
672 programStats.line_is_book = 0;
677 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678 if (appData.firstPlaysBlack) {
679 first.twoMachinesColor = "black\n";
680 second.twoMachinesColor = "white\n";
682 first.twoMachinesColor = "white\n";
683 second.twoMachinesColor = "black\n";
686 first.other = &second;
687 second.other = &first;
690 if(appData.timeOddsMode) {
691 norm = appData.timeOdds[0];
692 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694 first.timeOdds = appData.timeOdds[0]/norm;
695 second.timeOdds = appData.timeOdds[1]/norm;
698 if(programVersion) free(programVersion);
699 if (appData.noChessProgram) {
700 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701 sprintf(programVersion, "%s", PACKAGE_STRING);
703 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
710 UnloadEngine(ChessProgramState *cps)
712 /* Kill off first chess program */
713 if (cps->isr != NULL)
714 RemoveInputSource(cps->isr);
717 if (cps->pr != NoProc) {
719 DoSleep( appData.delayBeforeQuit );
720 SendToProgram("quit\n", cps);
721 DoSleep( appData.delayAfterQuit );
722 DestroyChildProcess(cps->pr, cps->useSigterm);
725 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
729 ClearOptions(ChessProgramState *cps)
732 cps->nrOptions = cps->comboCnt = 0;
733 for(i=0; i<MAX_OPTIONS; i++) {
734 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735 cps->option[i].textValue = 0;
739 char *engineNames[] = {
745 InitEngine(ChessProgramState *cps, int n)
746 { // [HGM] all engine initialiation put in a function that does one engine
750 cps->which = engineNames[n];
751 cps->maybeThinking = FALSE;
755 cps->sendDrawOffers = 1;
757 cps->program = appData.chessProgram[n];
758 cps->host = appData.host[n];
759 cps->dir = appData.directory[n];
760 cps->initString = appData.engInitString[n];
761 cps->computerString = appData.computerString[n];
762 cps->useSigint = TRUE;
763 cps->useSigterm = TRUE;
764 cps->reuse = appData.reuse[n];
765 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
766 cps->useSetboard = FALSE;
768 cps->usePing = FALSE;
771 cps->usePlayother = FALSE;
772 cps->useColors = TRUE;
773 cps->useUsermove = FALSE;
774 cps->sendICS = FALSE;
775 cps->sendName = appData.icsActive;
776 cps->sdKludge = FALSE;
777 cps->stKludge = FALSE;
778 TidyProgramName(cps->program, cps->host, cps->tidy);
780 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781 cps->analysisSupport = 2; /* detect */
782 cps->analyzing = FALSE;
783 cps->initDone = FALSE;
785 /* New features added by Tord: */
786 cps->useFEN960 = FALSE;
787 cps->useOOCastle = TRUE;
788 /* End of new features added by Tord. */
789 cps->fenOverride = appData.fenOverride[n];
791 /* [HGM] time odds: set factor for each machine */
792 cps->timeOdds = appData.timeOdds[n];
794 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795 cps->accumulateTC = appData.accumulateTC[n];
796 cps->maxNrOfSessions = 1;
801 cps->supportsNPS = UNKNOWN;
802 cps->memSize = FALSE;
803 cps->maxCores = FALSE;
804 cps->egtFormats[0] = NULLCHAR;
807 cps->optionSettings = appData.engOptions[n];
809 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810 cps->isUCI = appData.isUCI[n]; /* [AS] */
811 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813 if (appData.protocolVersion[n] > PROTOVER
814 || appData.protocolVersion[n] < 1)
819 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820 appData.protocolVersion[n]);
821 if( (len > MSG_SIZ) && appData.debugMode )
822 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824 DisplayFatalError(buf, 0, 2);
828 cps->protocolVersion = appData.protocolVersion[n];
831 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ChessProgramState *savCps;
840 if(WaitForEngine(savCps, LoadEngine)) return;
841 CommonEngineInit(); // recalculate time odds
842 if(gameInfo.variant != StringToVariant(appData.variant)) {
843 // we changed variant when loading the engine; this forces us to reset
844 Reset(TRUE, savCps != &first);
845 EditGameEvent(); // for consistency with other path, as Reset changes mode
847 InitChessProgram(savCps, FALSE);
848 SendToProgram("force\n", savCps);
849 DisplayMessage("", "");
850 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
857 ReplaceEngine(ChessProgramState *cps, int n)
861 appData.noChessProgram = FALSE;
862 appData.clockMode = TRUE;
864 if(n) return; // only startup first engine immediately; second can wait
865 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872 static char resetOptions[] =
873 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 Load(ChessProgramState *cps, int i)
879 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
880 if(engineLine[0]) { // an engine was selected from the combo box
881 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
900 snprintf(command, MSG_SIZ, "%s %s", p, params);
903 appData.chessProgram[i] = strdup(p);
904 appData.isUCI[i] = isUCI;
905 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906 appData.hasOwnBookUCI[i] = hasBook;
907 if(!nickName[0]) useNick = FALSE;
908 if(useNick) ASSIGN(appData.pgnName[i], nickName);
911 q = firstChessProgramNames;
912 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
914 useNick ? " -fn \"" : "",
915 useNick ? nickName : "",
917 v1 ? " -firstProtocolVersion 1" : "",
918 hasBook ? "" : " -fNoOwnBookUCI",
919 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920 storeVariant ? " -variant " : "",
921 storeVariant ? VariantName(gameInfo.variant) : "");
922 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
926 ReplaceEngine(cps, i);
932 int matched, min, sec;
934 * Parse timeControl resource
936 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937 appData.movesPerSession)) {
939 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940 DisplayFatalError(buf, 0, 2);
944 * Parse searchTime resource
946 if (*appData.searchTime != NULLCHAR) {
947 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
949 searchTime = min * 60;
950 } else if (matched == 2) {
951 searchTime = min * 60 + sec;
954 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955 DisplayFatalError(buf, 0, 2);
964 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
967 GetTimeMark(&programStartTime);
968 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
972 programStats.ok_to_send = 1;
973 programStats.seen_stat = 0;
976 * Initialize game list
982 * Internet chess server status
984 if (appData.icsActive) {
985 appData.matchMode = FALSE;
986 appData.matchGames = 0;
988 appData.noChessProgram = !appData.zippyPlay;
990 appData.zippyPlay = FALSE;
991 appData.zippyTalk = FALSE;
992 appData.noChessProgram = TRUE;
994 if (*appData.icsHelper != NULLCHAR) {
995 appData.useTelnet = TRUE;
996 appData.telnetProgram = appData.icsHelper;
999 appData.zippyTalk = appData.zippyPlay = FALSE;
1002 /* [AS] Initialize pv info list [HGM] and game state */
1006 for( i=0; i<=framePtr; i++ ) {
1007 pvInfoList[i].depth = -1;
1008 boards[i][EP_STATUS] = EP_NONE;
1009 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015 /* [AS] Adjudication threshold */
1016 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1018 InitEngine(&first, 0);
1019 InitEngine(&second, 1);
1022 if (appData.icsActive) {
1023 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1024 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1025 appData.clockMode = FALSE;
1026 first.sendTime = second.sendTime = 0;
1030 /* Override some settings from environment variables, for backward
1031 compatibility. Unfortunately it's not feasible to have the env
1032 vars just set defaults, at least in xboard. Ugh.
1034 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1039 if (!appData.icsActive) {
1043 /* Check for variants that are supported only in ICS mode,
1044 or not at all. Some that are accepted here nevertheless
1045 have bugs; see comments below.
1047 VariantClass variant = StringToVariant(appData.variant);
1049 case VariantBughouse: /* need four players and two boards */
1050 case VariantKriegspiel: /* need to hide pieces and move details */
1051 /* case VariantFischeRandom: (Fabien: moved below) */
1052 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1053 if( (len > MSG_SIZ) && appData.debugMode )
1054 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1056 DisplayFatalError(buf, 0, 2);
1059 case VariantUnknown:
1060 case VariantLoadable:
1070 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1071 if( (len > MSG_SIZ) && appData.debugMode )
1072 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1074 DisplayFatalError(buf, 0, 2);
1077 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1078 case VariantFairy: /* [HGM] TestLegality definitely off! */
1079 case VariantGothic: /* [HGM] should work */
1080 case VariantCapablanca: /* [HGM] should work */
1081 case VariantCourier: /* [HGM] initial forced moves not implemented */
1082 case VariantShogi: /* [HGM] could still mate with pawn drop */
1083 case VariantKnightmate: /* [HGM] should work */
1084 case VariantCylinder: /* [HGM] untested */
1085 case VariantFalcon: /* [HGM] untested */
1086 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1087 offboard interposition not understood */
1088 case VariantNormal: /* definitely works! */
1089 case VariantWildCastle: /* pieces not automatically shuffled */
1090 case VariantNoCastle: /* pieces not automatically shuffled */
1091 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1092 case VariantLosers: /* should work except for win condition,
1093 and doesn't know captures are mandatory */
1094 case VariantSuicide: /* should work except for win condition,
1095 and doesn't know captures are mandatory */
1096 case VariantGiveaway: /* should work except for win condition,
1097 and doesn't know captures are mandatory */
1098 case VariantTwoKings: /* should work */
1099 case VariantAtomic: /* should work except for win condition */
1100 case Variant3Check: /* should work except for win condition */
1101 case VariantShatranj: /* should work except for all win conditions */
1102 case VariantMakruk: /* should work except for daw countdown */
1103 case VariantBerolina: /* might work if TestLegality is off */
1104 case VariantCapaRandom: /* should work */
1105 case VariantJanus: /* should work */
1106 case VariantSuper: /* experimental */
1107 case VariantGreat: /* experimental, requires legality testing to be off */
1108 case VariantSChess: /* S-Chess, should work */
1109 case VariantSpartan: /* should work */
1116 int NextIntegerFromString( char ** str, long * value )
1121 while( *s == ' ' || *s == '\t' ) {
1127 if( *s >= '0' && *s <= '9' ) {
1128 while( *s >= '0' && *s <= '9' ) {
1129 *value = *value * 10 + (*s - '0');
1141 int NextTimeControlFromString( char ** str, long * value )
1144 int result = NextIntegerFromString( str, &temp );
1147 *value = temp * 60; /* Minutes */
1148 if( **str == ':' ) {
1150 result = NextIntegerFromString( str, &temp );
1151 *value += temp; /* Seconds */
1158 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1159 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1160 int result = -1, type = 0; long temp, temp2;
1162 if(**str != ':') return -1; // old params remain in force!
1164 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1165 if( NextIntegerFromString( str, &temp ) ) return -1;
1166 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1169 /* time only: incremental or sudden-death time control */
1170 if(**str == '+') { /* increment follows; read it */
1172 if(**str == '!') type = *(*str)++; // Bronstein TC
1173 if(result = NextIntegerFromString( str, &temp2)) return -1;
1174 *inc = temp2 * 1000;
1175 if(**str == '.') { // read fraction of increment
1176 char *start = ++(*str);
1177 if(result = NextIntegerFromString( str, &temp2)) return -1;
1179 while(start++ < *str) temp2 /= 10;
1183 *moves = 0; *tc = temp * 1000; *incType = type;
1187 (*str)++; /* classical time control */
1188 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1199 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1200 { /* [HGM] get time to add from the multi-session time-control string */
1201 int incType, moves=1; /* kludge to force reading of first session */
1202 long time, increment;
1205 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1206 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1208 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1209 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1210 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1211 if(movenr == -1) return time; /* last move before new session */
1212 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1213 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1214 if(!moves) return increment; /* current session is incremental */
1215 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1216 } while(movenr >= -1); /* try again for next session */
1218 return 0; // no new time quota on this move
1222 ParseTimeControl(tc, ti, mps)
1229 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1232 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1233 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1234 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1238 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1240 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1243 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1245 snprintf(buf, MSG_SIZ, ":%s", mytc);
1247 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1249 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1254 /* Parse second time control */
1257 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1265 timeControl_2 = tc2 * 1000;
1275 timeControl = tc1 * 1000;
1278 timeIncrement = ti * 1000; /* convert to ms */
1279 movesPerSession = 0;
1282 movesPerSession = mps;
1290 if (appData.debugMode) {
1291 fprintf(debugFP, "%s\n", programVersion);
1294 set_cont_sequence(appData.wrapContSeq);
1295 if (appData.matchGames > 0) {
1296 appData.matchMode = TRUE;
1297 } else if (appData.matchMode) {
1298 appData.matchGames = 1;
1300 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1301 appData.matchGames = appData.sameColorGames;
1302 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1303 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1304 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1307 if (appData.noChessProgram || first.protocolVersion == 1) {
1310 /* kludge: allow timeout for initial "feature" commands */
1312 DisplayMessage("", _("Starting chess program"));
1313 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1318 CalculateIndex(int index, int gameNr)
1319 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1321 if(index > 0) return index; // fixed nmber
1322 if(index == 0) return 1;
1323 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1324 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1329 LoadGameOrPosition(int gameNr)
1330 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1331 if (*appData.loadGameFile != NULLCHAR) {
1332 if (!LoadGameFromFile(appData.loadGameFile,
1333 CalculateIndex(appData.loadGameIndex, gameNr),
1334 appData.loadGameFile, FALSE)) {
1335 DisplayFatalError(_("Bad game file"), 0, 1);
1338 } else if (*appData.loadPositionFile != NULLCHAR) {
1339 if (!LoadPositionFromFile(appData.loadPositionFile,
1340 CalculateIndex(appData.loadPositionIndex, gameNr),
1341 appData.loadPositionFile)) {
1342 DisplayFatalError(_("Bad position file"), 0, 1);
1350 ReserveGame(int gameNr, char resChar)
1352 FILE *tf = fopen(appData.tourneyFile, "r+");
1353 char *p, *q, c, buf[MSG_SIZ];
1354 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1355 safeStrCpy(buf, lastMsg, MSG_SIZ);
1356 DisplayMessage(_("Pick new game"), "");
1357 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1358 ParseArgsFromFile(tf);
1359 p = q = appData.results;
1360 if(appData.debugMode) {
1361 char *r = appData.participants;
1362 fprintf(debugFP, "results = '%s'\n", p);
1363 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1364 fprintf(debugFP, "\n");
1366 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1368 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1369 safeStrCpy(q, p, strlen(p) + 2);
1370 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1371 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1372 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1373 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1376 fseek(tf, -(strlen(p)+4), SEEK_END);
1378 if(c != '"') // depending on DOS or Unix line endings we can be one off
1379 fseek(tf, -(strlen(p)+2), SEEK_END);
1380 else fseek(tf, -(strlen(p)+3), SEEK_END);
1381 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1382 DisplayMessage(buf, "");
1383 free(p); appData.results = q;
1384 if(nextGame <= appData.matchGames && resChar != ' ' &&
1385 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1386 UnloadEngine(&first); // next game belongs to other pairing;
1387 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1392 MatchEvent(int mode)
1393 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1395 if(matchMode) { // already in match mode: switch it off
1397 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1398 ModeHighlight(); // kludgey way to remove checkmark...
1401 // if(gameMode != BeginningOfGame) {
1402 // DisplayError(_("You can only start a match from the initial position."), 0);
1406 appData.matchGames = appData.defaultMatchGames;
1407 /* Set up machine vs. machine match */
1409 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1410 if(appData.tourneyFile[0]) {
1412 if(nextGame > appData.matchGames) {
1414 if(strchr(appData.results, '*') == NULL) {
1416 appData.tourneyCycles++;
1417 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1419 NextTourneyGame(-1, &dummy);
1421 if(nextGame <= appData.matchGames) {
1422 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1424 ScheduleDelayedEvent(NextMatchGame, 10000);
1429 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1430 DisplayError(buf, 0);
1431 appData.tourneyFile[0] = 0;
1435 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1436 DisplayFatalError(_("Can't have a match with no chess programs"),
1441 matchGame = roundNr = 1;
1442 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1447 InitBackEnd3 P((void))
1449 GameMode initialMode;
1453 InitChessProgram(&first, startedFromSetupPosition);
1455 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1456 free(programVersion);
1457 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1458 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1461 if (appData.icsActive) {
1463 /* [DM] Make a console window if needed [HGM] merged ifs */
1469 if (*appData.icsCommPort != NULLCHAR)
1470 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1471 appData.icsCommPort);
1473 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1474 appData.icsHost, appData.icsPort);
1476 if( (len > MSG_SIZ) && appData.debugMode )
1477 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1479 DisplayFatalError(buf, err, 1);
1484 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1486 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1487 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1488 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1489 } else if (appData.noChessProgram) {
1495 if (*appData.cmailGameName != NULLCHAR) {
1497 OpenLoopback(&cmailPR);
1499 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1503 DisplayMessage("", "");
1504 if (StrCaseCmp(appData.initialMode, "") == 0) {
1505 initialMode = BeginningOfGame;
1506 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1507 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1508 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1509 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1512 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1513 initialMode = TwoMachinesPlay;
1514 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1515 initialMode = AnalyzeFile;
1516 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1517 initialMode = AnalyzeMode;
1518 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1519 initialMode = MachinePlaysWhite;
1520 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1521 initialMode = MachinePlaysBlack;
1522 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1523 initialMode = EditGame;
1524 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1525 initialMode = EditPosition;
1526 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1527 initialMode = Training;
1529 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1530 if( (len > MSG_SIZ) && appData.debugMode )
1531 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533 DisplayFatalError(buf, 0, 2);
1537 if (appData.matchMode) {
1538 if(appData.tourneyFile[0]) { // start tourney from command line
1540 if(f = fopen(appData.tourneyFile, "r")) {
1541 ParseArgsFromFile(f); // make sure tourney parmeters re known
1543 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1546 } else if (*appData.cmailGameName != NULLCHAR) {
1547 /* Set up cmail mode */
1548 ReloadCmailMsgEvent(TRUE);
1550 /* Set up other modes */
1551 if (initialMode == AnalyzeFile) {
1552 if (*appData.loadGameFile == NULLCHAR) {
1553 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1557 if (*appData.loadGameFile != NULLCHAR) {
1558 (void) LoadGameFromFile(appData.loadGameFile,
1559 appData.loadGameIndex,
1560 appData.loadGameFile, TRUE);
1561 } else if (*appData.loadPositionFile != NULLCHAR) {
1562 (void) LoadPositionFromFile(appData.loadPositionFile,
1563 appData.loadPositionIndex,
1564 appData.loadPositionFile);
1565 /* [HGM] try to make self-starting even after FEN load */
1566 /* to allow automatic setup of fairy variants with wtm */
1567 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1568 gameMode = BeginningOfGame;
1569 setboardSpoiledMachineBlack = 1;
1571 /* [HGM] loadPos: make that every new game uses the setup */
1572 /* from file as long as we do not switch variant */
1573 if(!blackPlaysFirst) {
1574 startedFromPositionFile = TRUE;
1575 CopyBoard(filePosition, boards[0]);
1578 if (initialMode == AnalyzeMode) {
1579 if (appData.noChessProgram) {
1580 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1583 if (appData.icsActive) {
1584 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1588 } else if (initialMode == AnalyzeFile) {
1589 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1590 ShowThinkingEvent();
1592 AnalysisPeriodicEvent(1);
1593 } else if (initialMode == MachinePlaysWhite) {
1594 if (appData.noChessProgram) {
1595 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1599 if (appData.icsActive) {
1600 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1604 MachineWhiteEvent();
1605 } else if (initialMode == MachinePlaysBlack) {
1606 if (appData.noChessProgram) {
1607 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1611 if (appData.icsActive) {
1612 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1616 MachineBlackEvent();
1617 } else if (initialMode == TwoMachinesPlay) {
1618 if (appData.noChessProgram) {
1619 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1623 if (appData.icsActive) {
1624 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1629 } else if (initialMode == EditGame) {
1631 } else if (initialMode == EditPosition) {
1632 EditPositionEvent();
1633 } else if (initialMode == Training) {
1634 if (*appData.loadGameFile == NULLCHAR) {
1635 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1644 * Establish will establish a contact to a remote host.port.
1645 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1646 * used to talk to the host.
1647 * Returns 0 if okay, error code if not.
1654 if (*appData.icsCommPort != NULLCHAR) {
1655 /* Talk to the host through a serial comm port */
1656 return OpenCommPort(appData.icsCommPort, &icsPR);
1658 } else if (*appData.gateway != NULLCHAR) {
1659 if (*appData.remoteShell == NULLCHAR) {
1660 /* Use the rcmd protocol to run telnet program on a gateway host */
1661 snprintf(buf, sizeof(buf), "%s %s %s",
1662 appData.telnetProgram, appData.icsHost, appData.icsPort);
1663 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1666 /* Use the rsh program to run telnet program on a gateway host */
1667 if (*appData.remoteUser == NULLCHAR) {
1668 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1669 appData.gateway, appData.telnetProgram,
1670 appData.icsHost, appData.icsPort);
1672 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1673 appData.remoteShell, appData.gateway,
1674 appData.remoteUser, appData.telnetProgram,
1675 appData.icsHost, appData.icsPort);
1677 return StartChildProcess(buf, "", &icsPR);
1680 } else if (appData.useTelnet) {
1681 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1684 /* TCP socket interface differs somewhat between
1685 Unix and NT; handle details in the front end.
1687 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1691 void EscapeExpand(char *p, char *q)
1692 { // [HGM] initstring: routine to shape up string arguments
1693 while(*p++ = *q++) if(p[-1] == '\\')
1695 case 'n': p[-1] = '\n'; break;
1696 case 'r': p[-1] = '\r'; break;
1697 case 't': p[-1] = '\t'; break;
1698 case '\\': p[-1] = '\\'; break;
1699 case 0: *p = 0; return;
1700 default: p[-1] = q[-1]; break;
1705 show_bytes(fp, buf, count)
1711 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1712 fprintf(fp, "\\%03o", *buf & 0xff);
1721 /* Returns an errno value */
1723 OutputMaybeTelnet(pr, message, count, outError)
1729 char buf[8192], *p, *q, *buflim;
1730 int left, newcount, outcount;
1732 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1733 *appData.gateway != NULLCHAR) {
1734 if (appData.debugMode) {
1735 fprintf(debugFP, ">ICS: ");
1736 show_bytes(debugFP, message, count);
1737 fprintf(debugFP, "\n");
1739 return OutputToProcess(pr, message, count, outError);
1742 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1749 if (appData.debugMode) {
1750 fprintf(debugFP, ">ICS: ");
1751 show_bytes(debugFP, buf, newcount);
1752 fprintf(debugFP, "\n");
1754 outcount = OutputToProcess(pr, buf, newcount, outError);
1755 if (outcount < newcount) return -1; /* to be sure */
1762 } else if (((unsigned char) *p) == TN_IAC) {
1763 *q++ = (char) TN_IAC;
1770 if (appData.debugMode) {
1771 fprintf(debugFP, ">ICS: ");
1772 show_bytes(debugFP, buf, newcount);
1773 fprintf(debugFP, "\n");
1775 outcount = OutputToProcess(pr, buf, newcount, outError);
1776 if (outcount < newcount) return -1; /* to be sure */
1781 read_from_player(isr, closure, message, count, error)
1788 int outError, outCount;
1789 static int gotEof = 0;
1791 /* Pass data read from player on to ICS */
1794 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1795 if (outCount < count) {
1796 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1798 } else if (count < 0) {
1799 RemoveInputSource(isr);
1800 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1801 } else if (gotEof++ > 0) {
1802 RemoveInputSource(isr);
1803 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1809 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1810 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1811 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1812 SendToICS("date\n");
1813 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1816 /* added routine for printf style output to ics */
1817 void ics_printf(char *format, ...)
1819 char buffer[MSG_SIZ];
1822 va_start(args, format);
1823 vsnprintf(buffer, sizeof(buffer), format, args);
1824 buffer[sizeof(buffer)-1] = '\0';
1833 int count, outCount, outError;
1835 if (icsPR == NULL) return;
1838 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1839 if (outCount < count) {
1840 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1844 /* This is used for sending logon scripts to the ICS. Sending
1845 without a delay causes problems when using timestamp on ICC
1846 (at least on my machine). */
1848 SendToICSDelayed(s,msdelay)
1852 int count, outCount, outError;
1854 if (icsPR == NULL) return;
1857 if (appData.debugMode) {
1858 fprintf(debugFP, ">ICS: ");
1859 show_bytes(debugFP, s, count);
1860 fprintf(debugFP, "\n");
1862 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1864 if (outCount < count) {
1865 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870 /* Remove all highlighting escape sequences in s
1871 Also deletes any suffix starting with '('
1874 StripHighlightAndTitle(s)
1877 static char retbuf[MSG_SIZ];
1880 while (*s != NULLCHAR) {
1881 while (*s == '\033') {
1882 while (*s != NULLCHAR && !isalpha(*s)) s++;
1883 if (*s != NULLCHAR) s++;
1885 while (*s != NULLCHAR && *s != '\033') {
1886 if (*s == '(' || *s == '[') {
1897 /* Remove all highlighting escape sequences in s */
1902 static char retbuf[MSG_SIZ];
1905 while (*s != NULLCHAR) {
1906 while (*s == '\033') {
1907 while (*s != NULLCHAR && !isalpha(*s)) s++;
1908 if (*s != NULLCHAR) s++;
1910 while (*s != NULLCHAR && *s != '\033') {
1918 char *variantNames[] = VARIANT_NAMES;
1923 return variantNames[v];
1927 /* Identify a variant from the strings the chess servers use or the
1928 PGN Variant tag names we use. */
1935 VariantClass v = VariantNormal;
1936 int i, found = FALSE;
1942 /* [HGM] skip over optional board-size prefixes */
1943 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1944 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1945 while( *e++ != '_');
1948 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1952 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1953 if (StrCaseStr(e, variantNames[i])) {
1954 v = (VariantClass) i;
1961 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1962 || StrCaseStr(e, "wild/fr")
1963 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1964 v = VariantFischeRandom;
1965 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1966 (i = 1, p = StrCaseStr(e, "w"))) {
1968 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1975 case 0: /* FICS only, actually */
1977 /* Castling legal even if K starts on d-file */
1978 v = VariantWildCastle;
1983 /* Castling illegal even if K & R happen to start in
1984 normal positions. */
1985 v = VariantNoCastle;
1998 /* Castling legal iff K & R start in normal positions */
2004 /* Special wilds for position setup; unclear what to do here */
2005 v = VariantLoadable;
2008 /* Bizarre ICC game */
2009 v = VariantTwoKings;
2012 v = VariantKriegspiel;
2018 v = VariantFischeRandom;
2021 v = VariantCrazyhouse;
2024 v = VariantBughouse;
2030 /* Not quite the same as FICS suicide! */
2031 v = VariantGiveaway;
2037 v = VariantShatranj;
2040 /* Temporary names for future ICC types. The name *will* change in
2041 the next xboard/WinBoard release after ICC defines it. */
2079 v = VariantCapablanca;
2082 v = VariantKnightmate;
2088 v = VariantCylinder;
2094 v = VariantCapaRandom;
2097 v = VariantBerolina;
2109 /* Found "wild" or "w" in the string but no number;
2110 must assume it's normal chess. */
2114 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2115 if( (len > MSG_SIZ) && appData.debugMode )
2116 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2118 DisplayError(buf, 0);
2124 if (appData.debugMode) {
2125 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2126 e, wnum, VariantName(v));
2131 static int leftover_start = 0, leftover_len = 0;
2132 char star_match[STAR_MATCH_N][MSG_SIZ];
2134 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2135 advance *index beyond it, and set leftover_start to the new value of
2136 *index; else return FALSE. If pattern contains the character '*', it
2137 matches any sequence of characters not containing '\r', '\n', or the
2138 character following the '*' (if any), and the matched sequence(s) are
2139 copied into star_match.
2142 looking_at(buf, index, pattern)
2147 char *bufp = &buf[*index], *patternp = pattern;
2149 char *matchp = star_match[0];
2152 if (*patternp == NULLCHAR) {
2153 *index = leftover_start = bufp - buf;
2157 if (*bufp == NULLCHAR) return FALSE;
2158 if (*patternp == '*') {
2159 if (*bufp == *(patternp + 1)) {
2161 matchp = star_match[++star_count];
2165 } else if (*bufp == '\n' || *bufp == '\r') {
2167 if (*patternp == NULLCHAR)
2172 *matchp++ = *bufp++;
2176 if (*patternp != *bufp) return FALSE;
2183 SendToPlayer(data, length)
2187 int error, outCount;
2188 outCount = OutputToProcess(NoProc, data, length, &error);
2189 if (outCount < length) {
2190 DisplayFatalError(_("Error writing to display"), error, 1);
2195 PackHolding(packed, holding)
2207 switch (runlength) {
2218 sprintf(q, "%d", runlength);
2230 /* Telnet protocol requests from the front end */
2232 TelnetRequest(ddww, option)
2233 unsigned char ddww, option;
2235 unsigned char msg[3];
2236 int outCount, outError;
2238 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2240 if (appData.debugMode) {
2241 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2257 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2266 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2269 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2274 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2276 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2283 if (!appData.icsActive) return;
2284 TelnetRequest(TN_DO, TN_ECHO);
2290 if (!appData.icsActive) return;
2291 TelnetRequest(TN_DONT, TN_ECHO);
2295 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2297 /* put the holdings sent to us by the server on the board holdings area */
2298 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2302 if(gameInfo.holdingsWidth < 2) return;
2303 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2304 return; // prevent overwriting by pre-board holdings
2306 if( (int)lowestPiece >= BlackPawn ) {
2309 holdingsStartRow = BOARD_HEIGHT-1;
2312 holdingsColumn = BOARD_WIDTH-1;
2313 countsColumn = BOARD_WIDTH-2;
2314 holdingsStartRow = 0;
2318 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2319 board[i][holdingsColumn] = EmptySquare;
2320 board[i][countsColumn] = (ChessSquare) 0;
2322 while( (p=*holdings++) != NULLCHAR ) {
2323 piece = CharToPiece( ToUpper(p) );
2324 if(piece == EmptySquare) continue;
2325 /*j = (int) piece - (int) WhitePawn;*/
2326 j = PieceToNumber(piece);
2327 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2328 if(j < 0) continue; /* should not happen */
2329 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2330 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2331 board[holdingsStartRow+j*direction][countsColumn]++;
2337 VariantSwitch(Board board, VariantClass newVariant)
2339 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2340 static Board oldBoard;
2342 startedFromPositionFile = FALSE;
2343 if(gameInfo.variant == newVariant) return;
2345 /* [HGM] This routine is called each time an assignment is made to
2346 * gameInfo.variant during a game, to make sure the board sizes
2347 * are set to match the new variant. If that means adding or deleting
2348 * holdings, we shift the playing board accordingly
2349 * This kludge is needed because in ICS observe mode, we get boards
2350 * of an ongoing game without knowing the variant, and learn about the
2351 * latter only later. This can be because of the move list we requested,
2352 * in which case the game history is refilled from the beginning anyway,
2353 * but also when receiving holdings of a crazyhouse game. In the latter
2354 * case we want to add those holdings to the already received position.
2358 if (appData.debugMode) {
2359 fprintf(debugFP, "Switch board from %s to %s\n",
2360 VariantName(gameInfo.variant), VariantName(newVariant));
2361 setbuf(debugFP, NULL);
2363 shuffleOpenings = 0; /* [HGM] shuffle */
2364 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2368 newWidth = 9; newHeight = 9;
2369 gameInfo.holdingsSize = 7;
2370 case VariantBughouse:
2371 case VariantCrazyhouse:
2372 newHoldingsWidth = 2; break;
2376 newHoldingsWidth = 2;
2377 gameInfo.holdingsSize = 8;
2380 case VariantCapablanca:
2381 case VariantCapaRandom:
2384 newHoldingsWidth = gameInfo.holdingsSize = 0;
2387 if(newWidth != gameInfo.boardWidth ||
2388 newHeight != gameInfo.boardHeight ||
2389 newHoldingsWidth != gameInfo.holdingsWidth ) {
2391 /* shift position to new playing area, if needed */
2392 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2393 for(i=0; i<BOARD_HEIGHT; i++)
2394 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2395 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2397 for(i=0; i<newHeight; i++) {
2398 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2399 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2401 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2402 for(i=0; i<BOARD_HEIGHT; i++)
2403 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2404 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2407 gameInfo.boardWidth = newWidth;
2408 gameInfo.boardHeight = newHeight;
2409 gameInfo.holdingsWidth = newHoldingsWidth;
2410 gameInfo.variant = newVariant;
2411 InitDrawingSizes(-2, 0);
2412 } else gameInfo.variant = newVariant;
2413 CopyBoard(oldBoard, board); // remember correctly formatted board
2414 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2415 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2418 static int loggedOn = FALSE;
2420 /*-- Game start info cache: --*/
2422 char gs_kind[MSG_SIZ];
2423 static char player1Name[128] = "";
2424 static char player2Name[128] = "";
2425 static char cont_seq[] = "\n\\ ";
2426 static int player1Rating = -1;
2427 static int player2Rating = -1;
2428 /*----------------------------*/
2430 ColorClass curColor = ColorNormal;
2431 int suppressKibitz = 0;
2434 Boolean soughtPending = FALSE;
2435 Boolean seekGraphUp;
2436 #define MAX_SEEK_ADS 200
2438 char *seekAdList[MAX_SEEK_ADS];
2439 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2440 float tcList[MAX_SEEK_ADS];
2441 char colorList[MAX_SEEK_ADS];
2442 int nrOfSeekAds = 0;
2443 int minRating = 1010, maxRating = 2800;
2444 int hMargin = 10, vMargin = 20, h, w;
2445 extern int squareSize, lineGap;
2450 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2451 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2452 if(r < minRating+100 && r >=0 ) r = minRating+100;
2453 if(r > maxRating) r = maxRating;
2454 if(tc < 1.) tc = 1.;
2455 if(tc > 95.) tc = 95.;
2456 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2457 y = ((double)r - minRating)/(maxRating - minRating)
2458 * (h-vMargin-squareSize/8-1) + vMargin;
2459 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2460 if(strstr(seekAdList[i], " u ")) color = 1;
2461 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2462 !strstr(seekAdList[i], "bullet") &&
2463 !strstr(seekAdList[i], "blitz") &&
2464 !strstr(seekAdList[i], "standard") ) color = 2;
2465 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2466 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2470 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2472 char buf[MSG_SIZ], *ext = "";
2473 VariantClass v = StringToVariant(type);
2474 if(strstr(type, "wild")) {
2475 ext = type + 4; // append wild number
2476 if(v == VariantFischeRandom) type = "chess960"; else
2477 if(v == VariantLoadable) type = "setup"; else
2478 type = VariantName(v);
2480 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2481 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2482 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2483 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2484 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2485 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2486 seekNrList[nrOfSeekAds] = nr;
2487 zList[nrOfSeekAds] = 0;
2488 seekAdList[nrOfSeekAds++] = StrSave(buf);
2489 if(plot) PlotSeekAd(nrOfSeekAds-1);
2496 int x = xList[i], y = yList[i], d=squareSize/4, k;
2497 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2498 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2499 // now replot every dot that overlapped
2500 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2501 int xx = xList[k], yy = yList[k];
2502 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2503 DrawSeekDot(xx, yy, colorList[k]);
2508 RemoveSeekAd(int nr)
2511 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2513 if(seekAdList[i]) free(seekAdList[i]);
2514 seekAdList[i] = seekAdList[--nrOfSeekAds];
2515 seekNrList[i] = seekNrList[nrOfSeekAds];
2516 ratingList[i] = ratingList[nrOfSeekAds];
2517 colorList[i] = colorList[nrOfSeekAds];
2518 tcList[i] = tcList[nrOfSeekAds];
2519 xList[i] = xList[nrOfSeekAds];
2520 yList[i] = yList[nrOfSeekAds];
2521 zList[i] = zList[nrOfSeekAds];
2522 seekAdList[nrOfSeekAds] = NULL;
2528 MatchSoughtLine(char *line)
2530 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2531 int nr, base, inc, u=0; char dummy;
2533 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2534 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2536 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2537 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2538 // match: compact and save the line
2539 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2549 if(!seekGraphUp) return FALSE;
2550 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2551 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2553 DrawSeekBackground(0, 0, w, h);
2554 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2555 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2556 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2557 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2559 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2562 snprintf(buf, MSG_SIZ, "%d", i);
2563 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2566 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2567 for(i=1; i<100; i+=(i<10?1:5)) {
2568 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2569 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2570 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2572 snprintf(buf, MSG_SIZ, "%d", i);
2573 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2576 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2580 int SeekGraphClick(ClickType click, int x, int y, int moving)
2582 static int lastDown = 0, displayed = 0, lastSecond;
2583 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2584 if(click == Release || moving) return FALSE;
2586 soughtPending = TRUE;
2587 SendToICS(ics_prefix);
2588 SendToICS("sought\n"); // should this be "sought all"?
2589 } else { // issue challenge based on clicked ad
2590 int dist = 10000; int i, closest = 0, second = 0;
2591 for(i=0; i<nrOfSeekAds; i++) {
2592 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2593 if(d < dist) { dist = d; closest = i; }
2594 second += (d - zList[i] < 120); // count in-range ads
2595 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2599 second = (second > 1);
2600 if(displayed != closest || second != lastSecond) {
2601 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2602 lastSecond = second; displayed = closest;
2604 if(click == Press) {
2605 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2608 } // on press 'hit', only show info
2609 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2610 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2611 SendToICS(ics_prefix);
2613 return TRUE; // let incoming board of started game pop down the graph
2614 } else if(click == Release) { // release 'miss' is ignored
2615 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2616 if(moving == 2) { // right up-click
2617 nrOfSeekAds = 0; // refresh graph
2618 soughtPending = TRUE;
2619 SendToICS(ics_prefix);
2620 SendToICS("sought\n"); // should this be "sought all"?
2623 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2624 // press miss or release hit 'pop down' seek graph
2625 seekGraphUp = FALSE;
2626 DrawPosition(TRUE, NULL);
2632 read_from_ics(isr, closure, data, count, error)
2639 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2640 #define STARTED_NONE 0
2641 #define STARTED_MOVES 1
2642 #define STARTED_BOARD 2
2643 #define STARTED_OBSERVE 3
2644 #define STARTED_HOLDINGS 4
2645 #define STARTED_CHATTER 5
2646 #define STARTED_COMMENT 6
2647 #define STARTED_MOVES_NOHIDE 7
2649 static int started = STARTED_NONE;
2650 static char parse[20000];
2651 static int parse_pos = 0;
2652 static char buf[BUF_SIZE + 1];
2653 static int firstTime = TRUE, intfSet = FALSE;
2654 static ColorClass prevColor = ColorNormal;
2655 static int savingComment = FALSE;
2656 static int cmatch = 0; // continuation sequence match
2663 int backup; /* [DM] For zippy color lines */
2665 char talker[MSG_SIZ]; // [HGM] chat
2668 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2670 if (appData.debugMode) {
2672 fprintf(debugFP, "<ICS: ");
2673 show_bytes(debugFP, data, count);
2674 fprintf(debugFP, "\n");
2678 if (appData.debugMode) { int f = forwardMostMove;
2679 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2680 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2681 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2684 /* If last read ended with a partial line that we couldn't parse,
2685 prepend it to the new read and try again. */
2686 if (leftover_len > 0) {
2687 for (i=0; i<leftover_len; i++)
2688 buf[i] = buf[leftover_start + i];
2691 /* copy new characters into the buffer */
2692 bp = buf + leftover_len;
2693 buf_len=leftover_len;
2694 for (i=0; i<count; i++)
2697 if (data[i] == '\r')
2700 // join lines split by ICS?
2701 if (!appData.noJoin)
2704 Joining just consists of finding matches against the
2705 continuation sequence, and discarding that sequence
2706 if found instead of copying it. So, until a match
2707 fails, there's nothing to do since it might be the
2708 complete sequence, and thus, something we don't want
2711 if (data[i] == cont_seq[cmatch])
2714 if (cmatch == strlen(cont_seq))
2716 cmatch = 0; // complete match. just reset the counter
2719 it's possible for the ICS to not include the space
2720 at the end of the last word, making our [correct]
2721 join operation fuse two separate words. the server
2722 does this when the space occurs at the width setting.
2724 if (!buf_len || buf[buf_len-1] != ' ')
2735 match failed, so we have to copy what matched before
2736 falling through and copying this character. In reality,
2737 this will only ever be just the newline character, but
2738 it doesn't hurt to be precise.
2740 strncpy(bp, cont_seq, cmatch);
2752 buf[buf_len] = NULLCHAR;
2753 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2758 while (i < buf_len) {
2759 /* Deal with part of the TELNET option negotiation
2760 protocol. We refuse to do anything beyond the
2761 defaults, except that we allow the WILL ECHO option,
2762 which ICS uses to turn off password echoing when we are
2763 directly connected to it. We reject this option
2764 if localLineEditing mode is on (always on in xboard)
2765 and we are talking to port 23, which might be a real
2766 telnet server that will try to keep WILL ECHO on permanently.
2768 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2769 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2770 unsigned char option;
2772 switch ((unsigned char) buf[++i]) {
2774 if (appData.debugMode)
2775 fprintf(debugFP, "\n<WILL ");
2776 switch (option = (unsigned char) buf[++i]) {
2778 if (appData.debugMode)
2779 fprintf(debugFP, "ECHO ");
2780 /* Reply only if this is a change, according
2781 to the protocol rules. */
2782 if (remoteEchoOption) break;
2783 if (appData.localLineEditing &&
2784 atoi(appData.icsPort) == TN_PORT) {
2785 TelnetRequest(TN_DONT, TN_ECHO);
2788 TelnetRequest(TN_DO, TN_ECHO);
2789 remoteEchoOption = TRUE;
2793 if (appData.debugMode)
2794 fprintf(debugFP, "%d ", option);
2795 /* Whatever this is, we don't want it. */
2796 TelnetRequest(TN_DONT, option);
2801 if (appData.debugMode)
2802 fprintf(debugFP, "\n<WONT ");
2803 switch (option = (unsigned char) buf[++i]) {
2805 if (appData.debugMode)
2806 fprintf(debugFP, "ECHO ");
2807 /* Reply only if this is a change, according
2808 to the protocol rules. */
2809 if (!remoteEchoOption) break;
2811 TelnetRequest(TN_DONT, TN_ECHO);
2812 remoteEchoOption = FALSE;
2815 if (appData.debugMode)
2816 fprintf(debugFP, "%d ", (unsigned char) option);
2817 /* Whatever this is, it must already be turned
2818 off, because we never agree to turn on
2819 anything non-default, so according to the
2820 protocol rules, we don't reply. */
2825 if (appData.debugMode)
2826 fprintf(debugFP, "\n<DO ");
2827 switch (option = (unsigned char) buf[++i]) {
2829 /* Whatever this is, we refuse to do it. */
2830 if (appData.debugMode)
2831 fprintf(debugFP, "%d ", option);
2832 TelnetRequest(TN_WONT, option);
2837 if (appData.debugMode)
2838 fprintf(debugFP, "\n<DONT ");
2839 switch (option = (unsigned char) buf[++i]) {
2841 if (appData.debugMode)
2842 fprintf(debugFP, "%d ", option);
2843 /* Whatever this is, we are already not doing
2844 it, because we never agree to do anything
2845 non-default, so according to the protocol
2846 rules, we don't reply. */
2851 if (appData.debugMode)
2852 fprintf(debugFP, "\n<IAC ");
2853 /* Doubled IAC; pass it through */
2857 if (appData.debugMode)
2858 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2859 /* Drop all other telnet commands on the floor */
2862 if (oldi > next_out)
2863 SendToPlayer(&buf[next_out], oldi - next_out);
2869 /* OK, this at least will *usually* work */
2870 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2874 if (loggedOn && !intfSet) {
2875 if (ics_type == ICS_ICC) {
2876 snprintf(str, MSG_SIZ,
2877 "/set-quietly interface %s\n/set-quietly style 12\n",
2879 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2880 strcat(str, "/set-2 51 1\n/set seek 1\n");
2881 } else if (ics_type == ICS_CHESSNET) {
2882 snprintf(str, MSG_SIZ, "/style 12\n");
2884 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2885 strcat(str, programVersion);
2886 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2887 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2888 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2890 strcat(str, "$iset nohighlight 1\n");
2892 strcat(str, "$iset lock 1\n$style 12\n");
2895 NotifyFrontendLogin();
2899 if (started == STARTED_COMMENT) {
2900 /* Accumulate characters in comment */
2901 parse[parse_pos++] = buf[i];
2902 if (buf[i] == '\n') {
2903 parse[parse_pos] = NULLCHAR;
2904 if(chattingPartner>=0) {
2906 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2907 OutputChatMessage(chattingPartner, mess);
2908 chattingPartner = -1;
2909 next_out = i+1; // [HGM] suppress printing in ICS window
2911 if(!suppressKibitz) // [HGM] kibitz
2912 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2913 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2914 int nrDigit = 0, nrAlph = 0, j;
2915 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2916 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2917 parse[parse_pos] = NULLCHAR;
2918 // try to be smart: if it does not look like search info, it should go to
2919 // ICS interaction window after all, not to engine-output window.
2920 for(j=0; j<parse_pos; j++) { // count letters and digits
2921 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2922 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2923 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2925 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2926 int depth=0; float score;
2927 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2928 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2929 pvInfoList[forwardMostMove-1].depth = depth;
2930 pvInfoList[forwardMostMove-1].score = 100*score;
2932 OutputKibitz(suppressKibitz, parse);
2935 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936 SendToPlayer(tmp, strlen(tmp));
2938 next_out = i+1; // [HGM] suppress printing in ICS window
2940 started = STARTED_NONE;
2942 /* Don't match patterns against characters in comment */
2947 if (started == STARTED_CHATTER) {
2948 if (buf[i] != '\n') {
2949 /* Don't match patterns against characters in chatter */
2953 started = STARTED_NONE;
2954 if(suppressKibitz) next_out = i+1;
2957 /* Kludge to deal with rcmd protocol */
2958 if (firstTime && looking_at(buf, &i, "\001*")) {
2959 DisplayFatalError(&buf[1], 0, 1);
2965 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2968 if (appData.debugMode)
2969 fprintf(debugFP, "ics_type %d\n", ics_type);
2972 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973 ics_type = ICS_FICS;
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ics_type %d\n", ics_type);
2979 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980 ics_type = ICS_CHESSNET;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2988 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989 looking_at(buf, &i, "Logging you in as \"*\"") ||
2990 looking_at(buf, &i, "will be \"*\""))) {
2991 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2995 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2997 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998 DisplayIcsInteractionTitle(buf);
2999 have_set_title = TRUE;
3002 /* skip finger notes */
3003 if (started == STARTED_NONE &&
3004 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005 (buf[i] == '1' && buf[i+1] == '0')) &&
3006 buf[i+2] == ':' && buf[i+3] == ' ') {
3007 started = STARTED_CHATTER;
3013 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014 if(appData.seekGraph) {
3015 if(soughtPending && MatchSoughtLine(buf+i)) {
3016 i = strstr(buf+i, "rated") - buf;
3017 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018 next_out = leftover_start = i;
3019 started = STARTED_CHATTER;
3020 suppressKibitz = TRUE;
3023 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024 && looking_at(buf, &i, "* ads displayed")) {
3025 soughtPending = FALSE;
3030 if(appData.autoRefresh) {
3031 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032 int s = (ics_type == ICS_ICC); // ICC format differs
3034 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036 looking_at(buf, &i, "*% "); // eat prompt
3037 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039 next_out = i; // suppress
3042 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043 char *p = star_match[0];
3045 if(seekGraphUp) RemoveSeekAd(atoi(p));
3046 while(*p && *p++ != ' '); // next
3048 looking_at(buf, &i, "*% "); // eat prompt
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056 /* skip formula vars */
3057 if (started == STARTED_NONE &&
3058 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059 started = STARTED_CHATTER;
3064 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065 if (appData.autoKibitz && started == STARTED_NONE &&
3066 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3067 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3069 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3070 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3071 suppressKibitz = TRUE;
3072 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3075 && (gameMode == IcsPlayingWhite)) ||
3076 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3077 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3078 started = STARTED_CHATTER; // own kibitz we simply discard
3080 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3081 parse_pos = 0; parse[0] = NULLCHAR;
3082 savingComment = TRUE;
3083 suppressKibitz = gameMode != IcsObserving ? 2 :
3084 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3088 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3089 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3090 && atoi(star_match[0])) {
3091 // suppress the acknowledgements of our own autoKibitz
3093 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3095 SendToPlayer(star_match[0], strlen(star_match[0]));
3096 if(looking_at(buf, &i, "*% ")) // eat prompt
3097 suppressKibitz = FALSE;
3101 } // [HGM] kibitz: end of patch
3103 // [HGM] chat: intercept tells by users for which we have an open chat window
3105 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3106 looking_at(buf, &i, "* whispers:") ||
3107 looking_at(buf, &i, "* kibitzes:") ||
3108 looking_at(buf, &i, "* shouts:") ||
3109 looking_at(buf, &i, "* c-shouts:") ||
3110 looking_at(buf, &i, "--> * ") ||
3111 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3112 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3113 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3114 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3116 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3117 chattingPartner = -1;
3119 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3120 for(p=0; p<MAX_CHAT; p++) {
3121 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3122 talker[0] = '['; strcat(talker, "] ");
3123 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3124 chattingPartner = p; break;
3127 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3128 for(p=0; p<MAX_CHAT; p++) {
3129 if(!strcmp("kibitzes", chatPartner[p])) {
3130 talker[0] = '['; strcat(talker, "] ");
3131 chattingPartner = p; break;
3134 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3135 for(p=0; p<MAX_CHAT; p++) {
3136 if(!strcmp("whispers", chatPartner[p])) {
3137 talker[0] = '['; strcat(talker, "] ");
3138 chattingPartner = p; break;
3141 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3142 if(buf[i-8] == '-' && buf[i-3] == 't')
3143 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3144 if(!strcmp("c-shouts", chatPartner[p])) {
3145 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3146 chattingPartner = p; break;
3149 if(chattingPartner < 0)
3150 for(p=0; p<MAX_CHAT; p++) {
3151 if(!strcmp("shouts", chatPartner[p])) {
3152 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3153 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3154 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3155 chattingPartner = p; break;
3159 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3160 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3161 talker[0] = 0; Colorize(ColorTell, FALSE);
3162 chattingPartner = p; break;
3164 if(chattingPartner<0) i = oldi; else {
3165 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3166 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3167 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3168 started = STARTED_COMMENT;
3169 parse_pos = 0; parse[0] = NULLCHAR;
3170 savingComment = 3 + chattingPartner; // counts as TRUE
3171 suppressKibitz = TRUE;
3174 } // [HGM] chat: end of patch
3177 if (appData.zippyTalk || appData.zippyPlay) {
3178 /* [DM] Backup address for color zippy lines */
3180 if (loggedOn == TRUE)
3181 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3182 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3184 } // [DM] 'else { ' deleted
3186 /* Regular tells and says */
3187 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3188 looking_at(buf, &i, "* (your partner) tells you: ") ||
3189 looking_at(buf, &i, "* says: ") ||
3190 /* Don't color "message" or "messages" output */
3191 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3192 looking_at(buf, &i, "*. * at *:*: ") ||
3193 looking_at(buf, &i, "--* (*:*): ") ||
3194 /* Message notifications (same color as tells) */
3195 looking_at(buf, &i, "* has left a message ") ||
3196 looking_at(buf, &i, "* just sent you a message:\n") ||
3197 /* Whispers and kibitzes */
3198 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3199 looking_at(buf, &i, "* kibitzes: ") ||
3201 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3203 if (tkind == 1 && strchr(star_match[0], ':')) {
3204 /* Avoid "tells you:" spoofs in channels */
3207 if (star_match[0][0] == NULLCHAR ||
3208 strchr(star_match[0], ' ') ||
3209 (tkind == 3 && strchr(star_match[1], ' '))) {
3210 /* Reject bogus matches */
3213 if (appData.colorize) {
3214 if (oldi > next_out) {
3215 SendToPlayer(&buf[next_out], oldi - next_out);
3220 Colorize(ColorTell, FALSE);
3221 curColor = ColorTell;
3224 Colorize(ColorKibitz, FALSE);
3225 curColor = ColorKibitz;
3228 p = strrchr(star_match[1], '(');
3235 Colorize(ColorChannel1, FALSE);
3236 curColor = ColorChannel1;
3238 Colorize(ColorChannel, FALSE);
3239 curColor = ColorChannel;
3243 curColor = ColorNormal;
3247 if (started == STARTED_NONE && appData.autoComment &&
3248 (gameMode == IcsObserving ||
3249 gameMode == IcsPlayingWhite ||
3250 gameMode == IcsPlayingBlack)) {
3251 parse_pos = i - oldi;
3252 memcpy(parse, &buf[oldi], parse_pos);
3253 parse[parse_pos] = NULLCHAR;
3254 started = STARTED_COMMENT;
3255 savingComment = TRUE;
3257 started = STARTED_CHATTER;
3258 savingComment = FALSE;
3265 if (looking_at(buf, &i, "* s-shouts: ") ||
3266 looking_at(buf, &i, "* c-shouts: ")) {
3267 if (appData.colorize) {
3268 if (oldi > next_out) {
3269 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorSShout, FALSE);
3273 curColor = ColorSShout;
3276 started = STARTED_CHATTER;
3280 if (looking_at(buf, &i, "--->")) {
3285 if (looking_at(buf, &i, "* shouts: ") ||
3286 looking_at(buf, &i, "--> ")) {
3287 if (appData.colorize) {
3288 if (oldi > next_out) {
3289 SendToPlayer(&buf[next_out], oldi - next_out);
3292 Colorize(ColorShout, FALSE);
3293 curColor = ColorShout;
3296 started = STARTED_CHATTER;
3300 if (looking_at( buf, &i, "Challenge:")) {
3301 if (appData.colorize) {
3302 if (oldi > next_out) {
3303 SendToPlayer(&buf[next_out], oldi - next_out);
3306 Colorize(ColorChallenge, FALSE);
3307 curColor = ColorChallenge;
3313 if (looking_at(buf, &i, "* offers you") ||
3314 looking_at(buf, &i, "* offers to be") ||
3315 looking_at(buf, &i, "* would like to") ||
3316 looking_at(buf, &i, "* requests to") ||
3317 looking_at(buf, &i, "Your opponent offers") ||
3318 looking_at(buf, &i, "Your opponent requests")) {
3320 if (appData.colorize) {
3321 if (oldi > next_out) {
3322 SendToPlayer(&buf[next_out], oldi - next_out);
3325 Colorize(ColorRequest, FALSE);
3326 curColor = ColorRequest;
3331 if (looking_at(buf, &i, "* (*) seeking")) {
3332 if (appData.colorize) {
3333 if (oldi > next_out) {
3334 SendToPlayer(&buf[next_out], oldi - next_out);
3337 Colorize(ColorSeek, FALSE);
3338 curColor = ColorSeek;
3343 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3345 if (looking_at(buf, &i, "\\ ")) {
3346 if (prevColor != ColorNormal) {
3347 if (oldi > next_out) {
3348 SendToPlayer(&buf[next_out], oldi - next_out);
3351 Colorize(prevColor, TRUE);
3352 curColor = prevColor;
3354 if (savingComment) {
3355 parse_pos = i - oldi;
3356 memcpy(parse, &buf[oldi], parse_pos);
3357 parse[parse_pos] = NULLCHAR;
3358 started = STARTED_COMMENT;
3359 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3360 chattingPartner = savingComment - 3; // kludge to remember the box
3362 started = STARTED_CHATTER;
3367 if (looking_at(buf, &i, "Black Strength :") ||
3368 looking_at(buf, &i, "<<< style 10 board >>>") ||
3369 looking_at(buf, &i, "<10>") ||
3370 looking_at(buf, &i, "#@#")) {
3371 /* Wrong board style */
3373 SendToICS(ics_prefix);
3374 SendToICS("set style 12\n");
3375 SendToICS(ics_prefix);
3376 SendToICS("refresh\n");
3380 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3382 have_sent_ICS_logon = 1;
3386 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3387 (looking_at(buf, &i, "\n<12> ") ||
3388 looking_at(buf, &i, "<12> "))) {
3390 if (oldi > next_out) {
3391 SendToPlayer(&buf[next_out], oldi - next_out);
3394 started = STARTED_BOARD;
3399 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3400 looking_at(buf, &i, "<b1> ")) {
3401 if (oldi > next_out) {
3402 SendToPlayer(&buf[next_out], oldi - next_out);
3405 started = STARTED_HOLDINGS;
3410 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3412 /* Header for a move list -- first line */
3414 switch (ics_getting_history) {
3418 case BeginningOfGame:
3419 /* User typed "moves" or "oldmoves" while we
3420 were idle. Pretend we asked for these
3421 moves and soak them up so user can step
3422 through them and/or save them.
3425 gameMode = IcsObserving;
3428 ics_getting_history = H_GOT_UNREQ_HEADER;
3430 case EditGame: /*?*/
3431 case EditPosition: /*?*/
3432 /* Should above feature work in these modes too? */
3433 /* For now it doesn't */
3434 ics_getting_history = H_GOT_UNWANTED_HEADER;
3437 ics_getting_history = H_GOT_UNWANTED_HEADER;
3442 /* Is this the right one? */
3443 if (gameInfo.white && gameInfo.black &&
3444 strcmp(gameInfo.white, star_match[0]) == 0 &&
3445 strcmp(gameInfo.black, star_match[2]) == 0) {
3447 ics_getting_history = H_GOT_REQ_HEADER;
3450 case H_GOT_REQ_HEADER:
3451 case H_GOT_UNREQ_HEADER:
3452 case H_GOT_UNWANTED_HEADER:
3453 case H_GETTING_MOVES:
3454 /* Should not happen */
3455 DisplayError(_("Error gathering move list: two headers"), 0);
3456 ics_getting_history = H_FALSE;
3460 /* Save player ratings into gameInfo if needed */
3461 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3462 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3463 (gameInfo.whiteRating == -1 ||
3464 gameInfo.blackRating == -1)) {
3466 gameInfo.whiteRating = string_to_rating(star_match[1]);
3467 gameInfo.blackRating = string_to_rating(star_match[3]);
3468 if (appData.debugMode)
3469 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3470 gameInfo.whiteRating, gameInfo.blackRating);
3475 if (looking_at(buf, &i,
3476 "* * match, initial time: * minute*, increment: * second")) {
3477 /* Header for a move list -- second line */
3478 /* Initial board will follow if this is a wild game */
3479 if (gameInfo.event != NULL) free(gameInfo.event);
3480 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3481 gameInfo.event = StrSave(str);
3482 /* [HGM] we switched variant. Translate boards if needed. */
3483 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3487 if (looking_at(buf, &i, "Move ")) {
3488 /* Beginning of a move list */
3489 switch (ics_getting_history) {
3491 /* Normally should not happen */
3492 /* Maybe user hit reset while we were parsing */
3495 /* Happens if we are ignoring a move list that is not
3496 * the one we just requested. Common if the user
3497 * tries to observe two games without turning off
3500 case H_GETTING_MOVES:
3501 /* Should not happen */
3502 DisplayError(_("Error gathering move list: nested"), 0);
3503 ics_getting_history = H_FALSE;
3505 case H_GOT_REQ_HEADER:
3506 ics_getting_history = H_GETTING_MOVES;
3507 started = STARTED_MOVES;
3509 if (oldi > next_out) {
3510 SendToPlayer(&buf[next_out], oldi - next_out);
3513 case H_GOT_UNREQ_HEADER:
3514 ics_getting_history = H_GETTING_MOVES;
3515 started = STARTED_MOVES_NOHIDE;
3518 case H_GOT_UNWANTED_HEADER:
3519 ics_getting_history = H_FALSE;
3525 if (looking_at(buf, &i, "% ") ||
3526 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3527 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3528 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3529 soughtPending = FALSE;
3533 if(suppressKibitz) next_out = i;
3534 savingComment = FALSE;
3538 case STARTED_MOVES_NOHIDE:
3539 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3540 parse[parse_pos + i - oldi] = NULLCHAR;
3541 ParseGameHistory(parse);
3543 if (appData.zippyPlay && first.initDone) {
3544 FeedMovesToProgram(&first, forwardMostMove);
3545 if (gameMode == IcsPlayingWhite) {
3546 if (WhiteOnMove(forwardMostMove)) {
3547 if (first.sendTime) {
3548 if (first.useColors) {
3549 SendToProgram("black\n", &first);
3551 SendTimeRemaining(&first, TRUE);
3553 if (first.useColors) {
3554 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3556 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3557 first.maybeThinking = TRUE;
3559 if (first.usePlayother) {
3560 if (first.sendTime) {
3561 SendTimeRemaining(&first, TRUE);
3563 SendToProgram("playother\n", &first);
3569 } else if (gameMode == IcsPlayingBlack) {
3570 if (!WhiteOnMove(forwardMostMove)) {
3571 if (first.sendTime) {
3572 if (first.useColors) {
3573 SendToProgram("white\n", &first);
3575 SendTimeRemaining(&first, FALSE);
3577 if (first.useColors) {
3578 SendToProgram("black\n", &first);
3580 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3581 first.maybeThinking = TRUE;
3583 if (first.usePlayother) {
3584 if (first.sendTime) {
3585 SendTimeRemaining(&first, FALSE);
3587 SendToProgram("playother\n", &first);
3596 if (gameMode == IcsObserving && ics_gamenum == -1) {
3597 /* Moves came from oldmoves or moves command
3598 while we weren't doing anything else.
3600 currentMove = forwardMostMove;
3601 ClearHighlights();/*!!could figure this out*/
3602 flipView = appData.flipView;
3603 DrawPosition(TRUE, boards[currentMove]);
3604 DisplayBothClocks();
3605 snprintf(str, MSG_SIZ, "%s vs. %s",
3606 gameInfo.white, gameInfo.black);
3610 /* Moves were history of an active game */
3611 if (gameInfo.resultDetails != NULL) {
3612 free(gameInfo.resultDetails);
3613 gameInfo.resultDetails = NULL;
3616 HistorySet(parseList, backwardMostMove,
3617 forwardMostMove, currentMove-1);
3618 DisplayMove(currentMove - 1);
3619 if (started == STARTED_MOVES) next_out = i;
3620 started = STARTED_NONE;
3621 ics_getting_history = H_FALSE;
3624 case STARTED_OBSERVE:
3625 started = STARTED_NONE;
3626 SendToICS(ics_prefix);
3627 SendToICS("refresh\n");
3633 if(bookHit) { // [HGM] book: simulate book reply
3634 static char bookMove[MSG_SIZ]; // a bit generous?
3636 programStats.nodes = programStats.depth = programStats.time =
3637 programStats.score = programStats.got_only_move = 0;
3638 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3640 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3641 strcat(bookMove, bookHit);
3642 HandleMachineMove(bookMove, &first);
3647 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3648 started == STARTED_HOLDINGS ||
3649 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3650 /* Accumulate characters in move list or board */
3651 parse[parse_pos++] = buf[i];
3654 /* Start of game messages. Mostly we detect start of game
3655 when the first board image arrives. On some versions
3656 of the ICS, though, we need to do a "refresh" after starting
3657 to observe in order to get the current board right away. */
3658 if (looking_at(buf, &i, "Adding game * to observation list")) {
3659 started = STARTED_OBSERVE;
3663 /* Handle auto-observe */
3664 if (appData.autoObserve &&
3665 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3666 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3668 /* Choose the player that was highlighted, if any. */
3669 if (star_match[0][0] == '\033' ||
3670 star_match[1][0] != '\033') {
3671 player = star_match[0];
3673 player = star_match[2];
3675 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3676 ics_prefix, StripHighlightAndTitle(player));
3679 /* Save ratings from notify string */
3680 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3681 player1Rating = string_to_rating(star_match[1]);
3682 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3683 player2Rating = string_to_rating(star_match[3]);
3685 if (appData.debugMode)
3687 "Ratings from 'Game notification:' %s %d, %s %d\n",
3688 player1Name, player1Rating,
3689 player2Name, player2Rating);
3694 /* Deal with automatic examine mode after a game,
3695 and with IcsObserving -> IcsExamining transition */
3696 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3697 looking_at(buf, &i, "has made you an examiner of game *")) {
3699 int gamenum = atoi(star_match[0]);
3700 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3701 gamenum == ics_gamenum) {
3702 /* We were already playing or observing this game;
3703 no need to refetch history */
3704 gameMode = IcsExamining;
3706 pauseExamForwardMostMove = forwardMostMove;
3707 } else if (currentMove < forwardMostMove) {
3708 ForwardInner(forwardMostMove);
3711 /* I don't think this case really can happen */
3712 SendToICS(ics_prefix);
3713 SendToICS("refresh\n");
3718 /* Error messages */
3719 // if (ics_user_moved) {
3720 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3721 if (looking_at(buf, &i, "Illegal move") ||
3722 looking_at(buf, &i, "Not a legal move") ||
3723 looking_at(buf, &i, "Your king is in check") ||
3724 looking_at(buf, &i, "It isn't your turn") ||
3725 looking_at(buf, &i, "It is not your move")) {
3727 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3728 currentMove = forwardMostMove-1;
3729 DisplayMove(currentMove - 1); /* before DMError */
3730 DrawPosition(FALSE, boards[currentMove]);
3731 SwitchClocks(forwardMostMove-1); // [HGM] race
3732 DisplayBothClocks();
3734 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3740 if (looking_at(buf, &i, "still have time") ||
3741 looking_at(buf, &i, "not out of time") ||
3742 looking_at(buf, &i, "either player is out of time") ||
3743 looking_at(buf, &i, "has timeseal; checking")) {
3744 /* We must have called his flag a little too soon */
3745 whiteFlag = blackFlag = FALSE;
3749 if (looking_at(buf, &i, "added * seconds to") ||
3750 looking_at(buf, &i, "seconds were added to")) {
3751 /* Update the clocks */
3752 SendToICS(ics_prefix);
3753 SendToICS("refresh\n");
3757 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3758 ics_clock_paused = TRUE;
3763 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3764 ics_clock_paused = FALSE;
3769 /* Grab player ratings from the Creating: message.
3770 Note we have to check for the special case when
3771 the ICS inserts things like [white] or [black]. */
3772 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3773 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3775 0 player 1 name (not necessarily white)
3777 2 empty, white, or black (IGNORED)
3778 3 player 2 name (not necessarily black)
3781 The names/ratings are sorted out when the game
3782 actually starts (below).
3784 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3785 player1Rating = string_to_rating(star_match[1]);
3786 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3787 player2Rating = string_to_rating(star_match[4]);
3789 if (appData.debugMode)
3791 "Ratings from 'Creating:' %s %d, %s %d\n",
3792 player1Name, player1Rating,
3793 player2Name, player2Rating);
3798 /* Improved generic start/end-of-game messages */
3799 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3800 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3801 /* If tkind == 0: */
3802 /* star_match[0] is the game number */
3803 /* [1] is the white player's name */
3804 /* [2] is the black player's name */
3805 /* For end-of-game: */
3806 /* [3] is the reason for the game end */
3807 /* [4] is a PGN end game-token, preceded by " " */
3808 /* For start-of-game: */
3809 /* [3] begins with "Creating" or "Continuing" */
3810 /* [4] is " *" or empty (don't care). */
3811 int gamenum = atoi(star_match[0]);
3812 char *whitename, *blackname, *why, *endtoken;
3813 ChessMove endtype = EndOfFile;
3816 whitename = star_match[1];
3817 blackname = star_match[2];
3818 why = star_match[3];
3819 endtoken = star_match[4];
3821 whitename = star_match[1];
3822 blackname = star_match[3];
3823 why = star_match[5];
3824 endtoken = star_match[6];
3827 /* Game start messages */
3828 if (strncmp(why, "Creating ", 9) == 0 ||
3829 strncmp(why, "Continuing ", 11) == 0) {
3830 gs_gamenum = gamenum;
3831 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3832 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3834 if (appData.zippyPlay) {
3835 ZippyGameStart(whitename, blackname);
3838 partnerBoardValid = FALSE; // [HGM] bughouse
3842 /* Game end messages */
3843 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3844 ics_gamenum != gamenum) {
3847 while (endtoken[0] == ' ') endtoken++;
3848 switch (endtoken[0]) {
3851 endtype = GameUnfinished;
3854 endtype = BlackWins;
3857 if (endtoken[1] == '/')
3858 endtype = GameIsDrawn;
3860 endtype = WhiteWins;
3863 GameEnds(endtype, why, GE_ICS);
3865 if (appData.zippyPlay && first.initDone) {
3866 ZippyGameEnd(endtype, why);
3867 if (first.pr == NULL) {
3868 /* Start the next process early so that we'll
3869 be ready for the next challenge */
3870 StartChessProgram(&first);
3872 /* Send "new" early, in case this command takes
3873 a long time to finish, so that we'll be ready
3874 for the next challenge. */
3875 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3879 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3883 if (looking_at(buf, &i, "Removing game * from observation") ||
3884 looking_at(buf, &i, "no longer observing game *") ||
3885 looking_at(buf, &i, "Game * (*) has no examiners")) {
3886 if (gameMode == IcsObserving &&
3887 atoi(star_match[0]) == ics_gamenum)
3889 /* icsEngineAnalyze */
3890 if (appData.icsEngineAnalyze) {
3897 ics_user_moved = FALSE;
3902 if (looking_at(buf, &i, "no longer examining game *")) {
3903 if (gameMode == IcsExamining &&
3904 atoi(star_match[0]) == ics_gamenum)
3908 ics_user_moved = FALSE;
3913 /* Advance leftover_start past any newlines we find,
3914 so only partial lines can get reparsed */
3915 if (looking_at(buf, &i, "\n")) {
3916 prevColor = curColor;
3917 if (curColor != ColorNormal) {
3918 if (oldi > next_out) {
3919 SendToPlayer(&buf[next_out], oldi - next_out);
3922 Colorize(ColorNormal, FALSE);
3923 curColor = ColorNormal;
3925 if (started == STARTED_BOARD) {
3926 started = STARTED_NONE;
3927 parse[parse_pos] = NULLCHAR;
3928 ParseBoard12(parse);
3931 /* Send premove here */
3932 if (appData.premove) {
3934 if (currentMove == 0 &&
3935 gameMode == IcsPlayingWhite &&
3936 appData.premoveWhite) {
3937 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3938 if (appData.debugMode)
3939 fprintf(debugFP, "Sending premove:\n");
3941 } else if (currentMove == 1 &&
3942 gameMode == IcsPlayingBlack &&
3943 appData.premoveBlack) {
3944 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3945 if (appData.debugMode)
3946 fprintf(debugFP, "Sending premove:\n");
3948 } else if (gotPremove) {
3950 ClearPremoveHighlights();
3951 if (appData.debugMode)
3952 fprintf(debugFP, "Sending premove:\n");
3953 UserMoveEvent(premoveFromX, premoveFromY,
3954 premoveToX, premoveToY,
3959 /* Usually suppress following prompt */
3960 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3961 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3962 if (looking_at(buf, &i, "*% ")) {
3963 savingComment = FALSE;
3968 } else if (started == STARTED_HOLDINGS) {
3970 char new_piece[MSG_SIZ];
3971 started = STARTED_NONE;
3972 parse[parse_pos] = NULLCHAR;
3973 if (appData.debugMode)
3974 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3975 parse, currentMove);
3976 if (sscanf(parse, " game %d", &gamenum) == 1) {
3977 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3978 if (gameInfo.variant == VariantNormal) {
3979 /* [HGM] We seem to switch variant during a game!
3980 * Presumably no holdings were displayed, so we have
3981 * to move the position two files to the right to
3982 * create room for them!
3984 VariantClass newVariant;
3985 switch(gameInfo.boardWidth) { // base guess on board width
3986 case 9: newVariant = VariantShogi; break;
3987 case 10: newVariant = VariantGreat; break;
3988 default: newVariant = VariantCrazyhouse; break;
3990 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3991 /* Get a move list just to see the header, which
3992 will tell us whether this is really bug or zh */
3993 if (ics_getting_history == H_FALSE) {
3994 ics_getting_history = H_REQUESTED;
3995 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3999 new_piece[0] = NULLCHAR;
4000 sscanf(parse, "game %d white [%s black [%s <- %s",
4001 &gamenum, white_holding, black_holding,
4003 white_holding[strlen(white_holding)-1] = NULLCHAR;
4004 black_holding[strlen(black_holding)-1] = NULLCHAR;
4005 /* [HGM] copy holdings to board holdings area */
4006 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4007 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4008 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4010 if (appData.zippyPlay && first.initDone) {
4011 ZippyHoldings(white_holding, black_holding,
4015 if (tinyLayout || smallLayout) {
4016 char wh[16], bh[16];
4017 PackHolding(wh, white_holding);
4018 PackHolding(bh, black_holding);
4019 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4020 gameInfo.white, gameInfo.black);
4022 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4023 gameInfo.white, white_holding,
4024 gameInfo.black, black_holding);
4026 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4027 DrawPosition(FALSE, boards[currentMove]);
4029 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4030 sscanf(parse, "game %d white [%s black [%s <- %s",
4031 &gamenum, white_holding, black_holding,
4033 white_holding[strlen(white_holding)-1] = NULLCHAR;
4034 black_holding[strlen(black_holding)-1] = NULLCHAR;
4035 /* [HGM] copy holdings to partner-board holdings area */
4036 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4037 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4038 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4039 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4040 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4043 /* Suppress following prompt */
4044 if (looking_at(buf, &i, "*% ")) {
4045 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4046 savingComment = FALSE;
4054 i++; /* skip unparsed character and loop back */
4057 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4058 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4059 // SendToPlayer(&buf[next_out], i - next_out);
4060 started != STARTED_HOLDINGS && leftover_start > next_out) {
4061 SendToPlayer(&buf[next_out], leftover_start - next_out);
4065 leftover_len = buf_len - leftover_start;
4066 /* if buffer ends with something we couldn't parse,
4067 reparse it after appending the next read */
4069 } else if (count == 0) {
4070 RemoveInputSource(isr);
4071 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4073 DisplayFatalError(_("Error reading from ICS"), error, 1);
4078 /* Board style 12 looks like this:
4080 <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
4082 * The "<12> " is stripped before it gets to this routine. The two
4083 * trailing 0's (flip state and clock ticking) are later addition, and
4084 * some chess servers may not have them, or may have only the first.
4085 * Additional trailing fields may be added in the future.
4088 #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"
4090 #define RELATION_OBSERVING_PLAYED 0
4091 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4092 #define RELATION_PLAYING_MYMOVE 1
4093 #define RELATION_PLAYING_NOTMYMOVE -1
4094 #define RELATION_EXAMINING 2
4095 #define RELATION_ISOLATED_BOARD -3
4096 #define RELATION_STARTING_POSITION -4 /* FICS only */
4099 ParseBoard12(string)
4102 GameMode newGameMode;
4103 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4104 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4105 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4106 char to_play, board_chars[200];
4107 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4108 char black[32], white[32];
4110 int prevMove = currentMove;
4113 int fromX, fromY, toX, toY;
4115 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4116 char *bookHit = NULL; // [HGM] book
4117 Boolean weird = FALSE, reqFlag = FALSE;
4119 fromX = fromY = toX = toY = -1;
4123 if (appData.debugMode)
4124 fprintf(debugFP, _("Parsing board: %s\n"), string);
4126 move_str[0] = NULLCHAR;
4127 elapsed_time[0] = NULLCHAR;
4128 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4130 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4131 if(string[i] == ' ') { ranks++; files = 0; }
4133 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4136 for(j = 0; j <i; j++) board_chars[j] = string[j];
4137 board_chars[i] = '\0';
4140 n = sscanf(string, PATTERN, &to_play, &double_push,
4141 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4142 &gamenum, white, black, &relation, &basetime, &increment,
4143 &white_stren, &black_stren, &white_time, &black_time,
4144 &moveNum, str, elapsed_time, move_str, &ics_flip,
4148 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4149 DisplayError(str, 0);
4153 /* Convert the move number to internal form */
4154 moveNum = (moveNum - 1) * 2;
4155 if (to_play == 'B') moveNum++;
4156 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4157 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4163 case RELATION_OBSERVING_PLAYED:
4164 case RELATION_OBSERVING_STATIC:
4165 if (gamenum == -1) {
4166 /* Old ICC buglet */
4167 relation = RELATION_OBSERVING_STATIC;
4169 newGameMode = IcsObserving;
4171 case RELATION_PLAYING_MYMOVE:
4172 case RELATION_PLAYING_NOTMYMOVE:
4174 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4175 IcsPlayingWhite : IcsPlayingBlack;
4177 case RELATION_EXAMINING:
4178 newGameMode = IcsExamining;
4180 case RELATION_ISOLATED_BOARD:
4182 /* Just display this board. If user was doing something else,
4183 we will forget about it until the next board comes. */
4184 newGameMode = IcsIdle;
4186 case RELATION_STARTING_POSITION:
4187 newGameMode = gameMode;
4191 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4192 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4193 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4195 for (k = 0; k < ranks; k++) {
4196 for (j = 0; j < files; j++)
4197 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4198 if(gameInfo.holdingsWidth > 1) {
4199 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4200 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4203 CopyBoard(partnerBoard, board);
4204 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4205 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4206 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4207 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4208 if(toSqr = strchr(str, '-')) {
4209 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4210 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4211 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4212 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4213 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4214 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4215 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4216 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4217 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4218 DisplayMessage(partnerStatus, "");
4219 partnerBoardValid = TRUE;
4223 /* Modify behavior for initial board display on move listing
4226 switch (ics_getting_history) {
4230 case H_GOT_REQ_HEADER:
4231 case H_GOT_UNREQ_HEADER:
4232 /* This is the initial position of the current game */
4233 gamenum = ics_gamenum;
4234 moveNum = 0; /* old ICS bug workaround */
4235 if (to_play == 'B') {
4236 startedFromSetupPosition = TRUE;
4237 blackPlaysFirst = TRUE;
4239 if (forwardMostMove == 0) forwardMostMove = 1;
4240 if (backwardMostMove == 0) backwardMostMove = 1;
4241 if (currentMove == 0) currentMove = 1;
4243 newGameMode = gameMode;
4244 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4246 case H_GOT_UNWANTED_HEADER:
4247 /* This is an initial board that we don't want */
4249 case H_GETTING_MOVES:
4250 /* Should not happen */
4251 DisplayError(_("Error gathering move list: extra board"), 0);
4252 ics_getting_history = H_FALSE;
4256 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4257 weird && (int)gameInfo.variant < (int)VariantShogi) {
4258 /* [HGM] We seem to have switched variant unexpectedly
4259 * Try to guess new variant from board size
4261 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4262 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4263 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4264 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4265 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4266 if(!weird) newVariant = VariantNormal;
4267 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4268 /* Get a move list just to see the header, which
4269 will tell us whether this is really bug or zh */
4270 if (ics_getting_history == H_FALSE) {
4271 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4272 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4277 /* Take action if this is the first board of a new game, or of a
4278 different game than is currently being displayed. */
4279 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4280 relation == RELATION_ISOLATED_BOARD) {
4282 /* Forget the old game and get the history (if any) of the new one */
4283 if (gameMode != BeginningOfGame) {
4287 if (appData.autoRaiseBoard) BoardToTop();
4289 if (gamenum == -1) {
4290 newGameMode = IcsIdle;
4291 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4292 appData.getMoveList && !reqFlag) {
4293 /* Need to get game history */
4294 ics_getting_history = H_REQUESTED;
4295 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4299 /* Initially flip the board to have black on the bottom if playing
4300 black or if the ICS flip flag is set, but let the user change
4301 it with the Flip View button. */
4302 flipView = appData.autoFlipView ?
4303 (newGameMode == IcsPlayingBlack) || ics_flip :
4306 /* Done with values from previous mode; copy in new ones */
4307 gameMode = newGameMode;
4309 ics_gamenum = gamenum;
4310 if (gamenum == gs_gamenum) {
4311 int klen = strlen(gs_kind);
4312 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4313 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4314 gameInfo.event = StrSave(str);
4316 gameInfo.event = StrSave("ICS game");
4318 gameInfo.site = StrSave(appData.icsHost);
4319 gameInfo.date = PGNDate();
4320 gameInfo.round = StrSave("-");
4321 gameInfo.white = StrSave(white);
4322 gameInfo.black = StrSave(black);
4323 timeControl = basetime * 60 * 1000;
4325 timeIncrement = increment * 1000;
4326 movesPerSession = 0;
4327 gameInfo.timeControl = TimeControlTagValue();
4328 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4329 if (appData.debugMode) {
4330 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4331 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4332 setbuf(debugFP, NULL);
4335 gameInfo.outOfBook = NULL;
4337 /* Do we have the ratings? */
4338 if (strcmp(player1Name, white) == 0 &&
4339 strcmp(player2Name, black) == 0) {
4340 if (appData.debugMode)
4341 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4342 player1Rating, player2Rating);
4343 gameInfo.whiteRating = player1Rating;
4344 gameInfo.blackRating = player2Rating;
4345 } else if (strcmp(player2Name, white) == 0 &&
4346 strcmp(player1Name, black) == 0) {
4347 if (appData.debugMode)
4348 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4349 player2Rating, player1Rating);
4350 gameInfo.whiteRating = player2Rating;
4351 gameInfo.blackRating = player1Rating;
4353 player1Name[0] = player2Name[0] = NULLCHAR;
4355 /* Silence shouts if requested */
4356 if (appData.quietPlay &&
4357 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4358 SendToICS(ics_prefix);
4359 SendToICS("set shout 0\n");
4363 /* Deal with midgame name changes */
4365 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4366 if (gameInfo.white) free(gameInfo.white);
4367 gameInfo.white = StrSave(white);
4369 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4370 if (gameInfo.black) free(gameInfo.black);
4371 gameInfo.black = StrSave(black);
4375 /* Throw away game result if anything actually changes in examine mode */
4376 if (gameMode == IcsExamining && !newGame) {
4377 gameInfo.result = GameUnfinished;
4378 if (gameInfo.resultDetails != NULL) {
4379 free(gameInfo.resultDetails);
4380 gameInfo.resultDetails = NULL;
4384 /* In pausing && IcsExamining mode, we ignore boards coming
4385 in if they are in a different variation than we are. */
4386 if (pauseExamInvalid) return;
4387 if (pausing && gameMode == IcsExamining) {
4388 if (moveNum <= pauseExamForwardMostMove) {
4389 pauseExamInvalid = TRUE;
4390 forwardMostMove = pauseExamForwardMostMove;
4395 if (appData.debugMode) {
4396 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4398 /* Parse the board */
4399 for (k = 0; k < ranks; k++) {
4400 for (j = 0; j < files; j++)
4401 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4402 if(gameInfo.holdingsWidth > 1) {
4403 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4404 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4407 CopyBoard(boards[moveNum], board);
4408 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4410 startedFromSetupPosition =
4411 !CompareBoards(board, initialPosition);
4412 if(startedFromSetupPosition)
4413 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4416 /* [HGM] Set castling rights. Take the outermost Rooks,
4417 to make it also work for FRC opening positions. Note that board12
4418 is really defective for later FRC positions, as it has no way to
4419 indicate which Rook can castle if they are on the same side of King.
4420 For the initial position we grant rights to the outermost Rooks,
4421 and remember thos rights, and we then copy them on positions
4422 later in an FRC game. This means WB might not recognize castlings with
4423 Rooks that have moved back to their original position as illegal,
4424 but in ICS mode that is not its job anyway.
4426 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4427 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4429 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4430 if(board[0][i] == WhiteRook) j = i;
4431 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4432 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4433 if(board[0][i] == WhiteRook) j = i;
4434 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4435 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4436 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4437 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4438 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4439 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4440 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4443 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4444 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4445 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4446 if(board[BOARD_HEIGHT-1][k] == bKing)
4447 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4448 if(gameInfo.variant == VariantTwoKings) {
4449 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4450 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4451 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4454 r = boards[moveNum][CASTLING][0] = initialRights[0];
4455 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4456 r = boards[moveNum][CASTLING][1] = initialRights[1];
4457 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4458 r = boards[moveNum][CASTLING][3] = initialRights[3];
4459 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4460 r = boards[moveNum][CASTLING][4] = initialRights[4];
4461 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4462 /* wildcastle kludge: always assume King has rights */
4463 r = boards[moveNum][CASTLING][2] = initialRights[2];
4464 r = boards[moveNum][CASTLING][5] = initialRights[5];
4466 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4467 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4470 if (ics_getting_history == H_GOT_REQ_HEADER ||
4471 ics_getting_history == H_GOT_UNREQ_HEADER) {
4472 /* This was an initial position from a move list, not
4473 the current position */
4477 /* Update currentMove and known move number limits */
4478 newMove = newGame || moveNum > forwardMostMove;
4481 forwardMostMove = backwardMostMove = currentMove = moveNum;
4482 if (gameMode == IcsExamining && moveNum == 0) {
4483 /* Workaround for ICS limitation: we are not told the wild
4484 type when starting to examine a game. But if we ask for
4485 the move list, the move list header will tell us */
4486 ics_getting_history = H_REQUESTED;
4487 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4490 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4491 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4493 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4494 /* [HGM] applied this also to an engine that is silently watching */
4495 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4496 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4497 gameInfo.variant == currentlyInitializedVariant) {
4498 takeback = forwardMostMove - moveNum;
4499 for (i = 0; i < takeback; i++) {
4500 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4501 SendToProgram("undo\n", &first);
4506 forwardMostMove = moveNum;
4507 if (!pausing || currentMove > forwardMostMove)
4508 currentMove = forwardMostMove;
4510 /* New part of history that is not contiguous with old part */
4511 if (pausing && gameMode == IcsExamining) {
4512 pauseExamInvalid = TRUE;
4513 forwardMostMove = pauseExamForwardMostMove;
4516 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4518 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4519 // [HGM] when we will receive the move list we now request, it will be
4520 // fed to the engine from the first move on. So if the engine is not
4521 // in the initial position now, bring it there.
4522 InitChessProgram(&first, 0);
4525 ics_getting_history = H_REQUESTED;
4526 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4529 forwardMostMove = backwardMostMove = currentMove = moveNum;
4532 /* Update the clocks */
4533 if (strchr(elapsed_time, '.')) {
4535 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4536 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4538 /* Time is in seconds */
4539 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4540 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4545 if (appData.zippyPlay && newGame &&
4546 gameMode != IcsObserving && gameMode != IcsIdle &&
4547 gameMode != IcsExamining)
4548 ZippyFirstBoard(moveNum, basetime, increment);
4551 /* Put the move on the move list, first converting
4552 to canonical algebraic form. */
4554 if (appData.debugMode) {
4555 if (appData.debugMode) { int f = forwardMostMove;
4556 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4557 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4558 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4560 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4561 fprintf(debugFP, "moveNum = %d\n", moveNum);
4562 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4563 setbuf(debugFP, NULL);
4565 if (moveNum <= backwardMostMove) {
4566 /* We don't know what the board looked like before
4568 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4569 strcat(parseList[moveNum - 1], " ");
4570 strcat(parseList[moveNum - 1], elapsed_time);
4571 moveList[moveNum - 1][0] = NULLCHAR;
4572 } else if (strcmp(move_str, "none") == 0) {
4573 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4574 /* Again, we don't know what the board looked like;
4575 this is really the start of the game. */
4576 parseList[moveNum - 1][0] = NULLCHAR;
4577 moveList[moveNum - 1][0] = NULLCHAR;
4578 backwardMostMove = moveNum;
4579 startedFromSetupPosition = TRUE;
4580 fromX = fromY = toX = toY = -1;
4582 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4583 // So we parse the long-algebraic move string in stead of the SAN move
4584 int valid; char buf[MSG_SIZ], *prom;
4586 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4587 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4588 // str looks something like "Q/a1-a2"; kill the slash
4590 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4591 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4592 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4593 strcat(buf, prom); // long move lacks promo specification!
4594 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4595 if(appData.debugMode)
4596 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4597 safeStrCpy(move_str, buf, MSG_SIZ);
4599 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4600 &fromX, &fromY, &toX, &toY, &promoChar)
4601 || ParseOneMove(buf, moveNum - 1, &moveType,
4602 &fromX, &fromY, &toX, &toY, &promoChar);
4603 // end of long SAN patch
4605 (void) CoordsToAlgebraic(boards[moveNum - 1],
4606 PosFlags(moveNum - 1),
4607 fromY, fromX, toY, toX, promoChar,
4608 parseList[moveNum-1]);
4609 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4615 if(gameInfo.variant != VariantShogi)
4616 strcat(parseList[moveNum - 1], "+");
4619 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4620 strcat(parseList[moveNum - 1], "#");
4623 strcat(parseList[moveNum - 1], " ");
4624 strcat(parseList[moveNum - 1], elapsed_time);
4625 /* currentMoveString is set as a side-effect of ParseOneMove */
4626 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4627 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4628 strcat(moveList[moveNum - 1], "\n");
4630 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4631 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4632 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4633 ChessSquare old, new = boards[moveNum][k][j];
4634 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4635 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4636 if(old == new) continue;
4637 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4638 else if(new == WhiteWazir || new == BlackWazir) {
4639 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4640 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4641 else boards[moveNum][k][j] = old; // preserve type of Gold
4642 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4643 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4646 /* Move from ICS was illegal!? Punt. */
4647 if (appData.debugMode) {
4648 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4649 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4651 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4652 strcat(parseList[moveNum - 1], " ");
4653 strcat(parseList[moveNum - 1], elapsed_time);
4654 moveList[moveNum - 1][0] = NULLCHAR;
4655 fromX = fromY = toX = toY = -1;
4658 if (appData.debugMode) {
4659 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4660 setbuf(debugFP, NULL);
4664 /* Send move to chess program (BEFORE animating it). */
4665 if (appData.zippyPlay && !newGame && newMove &&
4666 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4668 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4669 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4670 if (moveList[moveNum - 1][0] == NULLCHAR) {
4671 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4673 DisplayError(str, 0);
4675 if (first.sendTime) {
4676 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4678 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4679 if (firstMove && !bookHit) {
4681 if (first.useColors) {
4682 SendToProgram(gameMode == IcsPlayingWhite ?
4684 "black\ngo\n", &first);
4686 SendToProgram("go\n", &first);
4688 first.maybeThinking = TRUE;
4691 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4692 if (moveList[moveNum - 1][0] == NULLCHAR) {
4693 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4694 DisplayError(str, 0);
4696 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4697 SendMoveToProgram(moveNum - 1, &first);
4704 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4705 /* If move comes from a remote source, animate it. If it
4706 isn't remote, it will have already been animated. */
4707 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4708 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4710 if (!pausing && appData.highlightLastMove) {
4711 SetHighlights(fromX, fromY, toX, toY);
4715 /* Start the clocks */
4716 whiteFlag = blackFlag = FALSE;
4717 appData.clockMode = !(basetime == 0 && increment == 0);
4719 ics_clock_paused = TRUE;
4721 } else if (ticking == 1) {
4722 ics_clock_paused = FALSE;
4724 if (gameMode == IcsIdle ||
4725 relation == RELATION_OBSERVING_STATIC ||
4726 relation == RELATION_EXAMINING ||
4728 DisplayBothClocks();
4732 /* Display opponents and material strengths */
4733 if (gameInfo.variant != VariantBughouse &&
4734 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4735 if (tinyLayout || smallLayout) {
4736 if(gameInfo.variant == VariantNormal)
4737 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4738 gameInfo.white, white_stren, gameInfo.black, black_stren,
4739 basetime, increment);
4741 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4742 gameInfo.white, white_stren, gameInfo.black, black_stren,
4743 basetime, increment, (int) gameInfo.variant);
4745 if(gameInfo.variant == VariantNormal)
4746 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4747 gameInfo.white, white_stren, gameInfo.black, black_stren,
4748 basetime, increment);
4750 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4751 gameInfo.white, white_stren, gameInfo.black, black_stren,
4752 basetime, increment, VariantName(gameInfo.variant));
4755 if (appData.debugMode) {
4756 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4761 /* Display the board */
4762 if (!pausing && !appData.noGUI) {
4764 if (appData.premove)
4766 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4767 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4768 ClearPremoveHighlights();
4770 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4771 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4772 DrawPosition(j, boards[currentMove]);
4774 DisplayMove(moveNum - 1);
4775 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4776 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4777 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4778 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4782 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4784 if(bookHit) { // [HGM] book: simulate book reply
4785 static char bookMove[MSG_SIZ]; // a bit generous?
4787 programStats.nodes = programStats.depth = programStats.time =
4788 programStats.score = programStats.got_only_move = 0;
4789 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4791 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4792 strcat(bookMove, bookHit);
4793 HandleMachineMove(bookMove, &first);
4802 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4803 ics_getting_history = H_REQUESTED;
4804 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4810 AnalysisPeriodicEvent(force)
4813 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4814 && !force) || !appData.periodicUpdates)
4817 /* Send . command to Crafty to collect stats */
4818 SendToProgram(".\n", &first);
4820 /* Don't send another until we get a response (this makes
4821 us stop sending to old Crafty's which don't understand
4822 the "." command (sending illegal cmds resets node count & time,
4823 which looks bad)) */
4824 programStats.ok_to_send = 0;
4827 void ics_update_width(new_width)
4830 ics_printf("set width %d\n", new_width);
4834 SendMoveToProgram(moveNum, cps)
4836 ChessProgramState *cps;
4840 if (cps->useUsermove) {
4841 SendToProgram("usermove ", cps);
4845 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4846 int len = space - parseList[moveNum];
4847 memcpy(buf, parseList[moveNum], len);
4849 buf[len] = NULLCHAR;
4851 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4853 SendToProgram(buf, cps);
4855 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4856 AlphaRank(moveList[moveNum], 4);
4857 SendToProgram(moveList[moveNum], cps);
4858 AlphaRank(moveList[moveNum], 4); // and back
4860 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4861 * the engine. It would be nice to have a better way to identify castle
4863 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4864 && cps->useOOCastle) {
4865 int fromX = moveList[moveNum][0] - AAA;
4866 int fromY = moveList[moveNum][1] - ONE;
4867 int toX = moveList[moveNum][2] - AAA;
4868 int toY = moveList[moveNum][3] - ONE;
4869 if((boards[moveNum][fromY][fromX] == WhiteKing
4870 && boards[moveNum][toY][toX] == WhiteRook)
4871 || (boards[moveNum][fromY][fromX] == BlackKing
4872 && boards[moveNum][toY][toX] == BlackRook)) {
4873 if(toX > fromX) SendToProgram("O-O\n", cps);
4874 else SendToProgram("O-O-O\n", cps);
4876 else SendToProgram(moveList[moveNum], cps);
4878 else SendToProgram(moveList[moveNum], cps);
4879 /* End of additions by Tord */
4882 /* [HGM] setting up the opening has brought engine in force mode! */
4883 /* Send 'go' if we are in a mode where machine should play. */
4884 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4885 (gameMode == TwoMachinesPlay ||
4887 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4889 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4890 SendToProgram("go\n", cps);
4891 if (appData.debugMode) {
4892 fprintf(debugFP, "(extra)\n");
4895 setboardSpoiledMachineBlack = 0;
4899 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4901 int fromX, fromY, toX, toY;
4904 char user_move[MSG_SIZ];
4908 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4909 (int)moveType, fromX, fromY, toX, toY);
4910 DisplayError(user_move + strlen("say "), 0);
4912 case WhiteKingSideCastle:
4913 case BlackKingSideCastle:
4914 case WhiteQueenSideCastleWild:
4915 case BlackQueenSideCastleWild:
4917 case WhiteHSideCastleFR:
4918 case BlackHSideCastleFR:
4920 snprintf(user_move, MSG_SIZ, "o-o\n");
4922 case WhiteQueenSideCastle:
4923 case BlackQueenSideCastle:
4924 case WhiteKingSideCastleWild:
4925 case BlackKingSideCastleWild:
4927 case WhiteASideCastleFR:
4928 case BlackASideCastleFR:
4930 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4932 case WhiteNonPromotion:
4933 case BlackNonPromotion:
4934 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4936 case WhitePromotion:
4937 case BlackPromotion:
4938 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4939 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4940 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4941 PieceToChar(WhiteFerz));
4942 else if(gameInfo.variant == VariantGreat)
4943 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4944 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4945 PieceToChar(WhiteMan));
4947 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4948 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4954 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4955 ToUpper(PieceToChar((ChessSquare) fromX)),
4956 AAA + toX, ONE + toY);
4958 case IllegalMove: /* could be a variant we don't quite understand */
4959 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4961 case WhiteCapturesEnPassant:
4962 case BlackCapturesEnPassant:
4963 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4964 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4967 SendToICS(user_move);
4968 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4969 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4974 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4975 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4976 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4977 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4978 DisplayError("You cannot do this while you are playing or observing", 0);
4981 if(gameMode != IcsExamining) { // is this ever not the case?
4982 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4984 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4985 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4986 } else { // on FICS we must first go to general examine mode
4987 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4989 if(gameInfo.variant != VariantNormal) {
4990 // try figure out wild number, as xboard names are not always valid on ICS
4991 for(i=1; i<=36; i++) {
4992 snprintf(buf, MSG_SIZ, "wild/%d", i);
4993 if(StringToVariant(buf) == gameInfo.variant) break;
4995 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4996 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4997 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4998 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4999 SendToICS(ics_prefix);
5001 if(startedFromSetupPosition || backwardMostMove != 0) {
5002 fen = PositionToFEN(backwardMostMove, NULL);
5003 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5004 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5006 } else { // FICS: everything has to set by separate bsetup commands
5007 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5008 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5010 if(!WhiteOnMove(backwardMostMove)) {
5011 SendToICS("bsetup tomove black\n");
5013 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5014 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5016 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5017 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5019 i = boards[backwardMostMove][EP_STATUS];
5020 if(i >= 0) { // set e.p.
5021 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5027 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5028 SendToICS("bsetup done\n"); // switch to normal examining.
5030 for(i = backwardMostMove; i<last; i++) {
5032 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5035 SendToICS(ics_prefix);
5036 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5040 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5045 if (rf == DROP_RANK) {
5046 sprintf(move, "%c@%c%c\n",
5047 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5049 if (promoChar == 'x' || promoChar == NULLCHAR) {
5050 sprintf(move, "%c%c%c%c\n",
5051 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5053 sprintf(move, "%c%c%c%c%c\n",
5054 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5060 ProcessICSInitScript(f)
5065 while (fgets(buf, MSG_SIZ, f)) {
5066 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5073 static int lastX, lastY, selectFlag, dragging;
5078 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5079 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5080 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5081 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5082 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5083 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5086 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5087 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5088 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5089 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5091 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5092 appData.testLegality && (promoSweep == king ||
5093 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5094 ChangeDragPiece(promoSweep);
5097 int PromoScroll(int x, int y)
5101 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5102 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5103 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5104 if(!step) return FALSE;
5105 lastX = x; lastY = y;
5106 if((promoSweep < BlackPawn) == flipView) step = -step;
5107 if(step > 0) selectFlag = 1;
5108 if(!selectFlag) Sweep(step);
5115 ChessSquare piece = boards[currentMove][toY][toX];
5118 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5119 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5120 if(!step) step = -1;
5121 } while(PieceToChar(pieceSweep) == '.');
5122 boards[currentMove][toY][toX] = pieceSweep;
5123 DrawPosition(FALSE, boards[currentMove]);
5124 boards[currentMove][toY][toX] = piece;
5126 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5128 AlphaRank(char *move, int n)
5130 // char *p = move, c; int x, y;
5132 if (appData.debugMode) {
5133 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5137 move[2]>='0' && move[2]<='9' &&
5138 move[3]>='a' && move[3]<='x' ) {
5140 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5141 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5143 if(move[0]>='0' && move[0]<='9' &&
5144 move[1]>='a' && move[1]<='x' &&
5145 move[2]>='0' && move[2]<='9' &&
5146 move[3]>='a' && move[3]<='x' ) {
5147 /* input move, Shogi -> normal */
5148 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5149 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5150 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5151 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5154 move[3]>='0' && move[3]<='9' &&
5155 move[2]>='a' && move[2]<='x' ) {
5157 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5158 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5161 move[0]>='a' && move[0]<='x' &&
5162 move[3]>='0' && move[3]<='9' &&
5163 move[2]>='a' && move[2]<='x' ) {
5164 /* output move, normal -> Shogi */
5165 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5166 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5167 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5168 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5169 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5171 if (appData.debugMode) {
5172 fprintf(debugFP, " out = '%s'\n", move);
5176 char yy_textstr[8000];
5178 /* Parser for moves from gnuchess, ICS, or user typein box */
5180 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5183 ChessMove *moveType;
5184 int *fromX, *fromY, *toX, *toY;
5187 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5189 switch (*moveType) {
5190 case WhitePromotion:
5191 case BlackPromotion:
5192 case WhiteNonPromotion:
5193 case BlackNonPromotion:
5195 case WhiteCapturesEnPassant:
5196 case BlackCapturesEnPassant:
5197 case WhiteKingSideCastle:
5198 case WhiteQueenSideCastle:
5199 case BlackKingSideCastle:
5200 case BlackQueenSideCastle:
5201 case WhiteKingSideCastleWild:
5202 case WhiteQueenSideCastleWild:
5203 case BlackKingSideCastleWild:
5204 case BlackQueenSideCastleWild:
5205 /* Code added by Tord: */
5206 case WhiteHSideCastleFR:
5207 case WhiteASideCastleFR:
5208 case BlackHSideCastleFR:
5209 case BlackASideCastleFR:
5210 /* End of code added by Tord */
5211 case IllegalMove: /* bug or odd chess variant */
5212 *fromX = currentMoveString[0] - AAA;
5213 *fromY = currentMoveString[1] - ONE;
5214 *toX = currentMoveString[2] - AAA;
5215 *toY = currentMoveString[3] - ONE;
5216 *promoChar = currentMoveString[4];
5217 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5218 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5219 if (appData.debugMode) {
5220 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5222 *fromX = *fromY = *toX = *toY = 0;
5225 if (appData.testLegality) {
5226 return (*moveType != IllegalMove);
5228 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5229 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5234 *fromX = *moveType == WhiteDrop ?
5235 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5236 (int) CharToPiece(ToLower(currentMoveString[0]));
5238 *toX = currentMoveString[2] - AAA;
5239 *toY = currentMoveString[3] - ONE;
5240 *promoChar = NULLCHAR;
5244 case ImpossibleMove:
5254 if (appData.debugMode) {
5255 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5258 *fromX = *fromY = *toX = *toY = 0;
5259 *promoChar = NULLCHAR;
5264 Boolean pushed = FALSE;
5267 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5268 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5269 int fromX, fromY, toX, toY; char promoChar;
5274 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5275 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5278 endPV = forwardMostMove;
5280 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5281 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5282 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5283 if(appData.debugMode){
5284 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);
5286 if(!valid && nr == 0 &&
5287 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5288 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5289 // Hande case where played move is different from leading PV move
5290 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5291 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5292 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5293 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5294 endPV += 2; // if position different, keep this
5295 moveList[endPV-1][0] = fromX + AAA;
5296 moveList[endPV-1][1] = fromY + ONE;
5297 moveList[endPV-1][2] = toX + AAA;
5298 moveList[endPV-1][3] = toY + ONE;
5299 parseList[endPV-1][0] = NULLCHAR;
5300 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5303 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5304 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5305 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5306 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5307 valid++; // allow comments in PV
5311 if(endPV+1 > framePtr) break; // no space, truncate
5314 CopyBoard(boards[endPV], boards[endPV-1]);
5315 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5316 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5317 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5318 CoordsToAlgebraic(boards[endPV - 1],
5319 PosFlags(endPV - 1),
5320 fromY, fromX, toY, toX, promoChar,
5321 parseList[endPV - 1]);
5323 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5324 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5325 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5326 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5327 DrawPosition(TRUE, boards[currentMove]);
5331 MultiPV(ChessProgramState *cps)
5332 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5334 for(i=0; i<cps->nrOptions; i++)
5335 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5341 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5343 int startPV, multi, lineStart, origIndex = index;
5344 char *p, buf2[MSG_SIZ];
5346 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5347 lastX = x; lastY = y;
5348 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5349 lineStart = startPV = index;
5350 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5351 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5353 do{ while(buf[index] && buf[index] != '\n') index++;
5354 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5356 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5357 int n = first.option[multi].value;
5358 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5359 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5360 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5361 first.option[multi].value = n;
5365 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5366 *start = startPV; *end = index-1;
5371 LoadPV(int x, int y)
5372 { // called on right mouse click to load PV
5373 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5374 lastX = x; lastY = y;
5375 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5382 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5383 if(endPV < 0) return;
5385 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5386 Boolean saveAnimate = appData.animate;
5388 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5389 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5390 } else storedGames--; // abandon shelved tail of original game
5393 forwardMostMove = currentMove;
5394 currentMove = oldFMM;
5395 appData.animate = FALSE;
5396 ToNrEvent(forwardMostMove);
5397 appData.animate = saveAnimate;
5399 currentMove = forwardMostMove;
5400 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5401 ClearPremoveHighlights();
5402 DrawPosition(TRUE, boards[currentMove]);
5406 MovePV(int x, int y, int h)
5407 { // step through PV based on mouse coordinates (called on mouse move)
5408 int margin = h>>3, step = 0;
5410 // we must somehow check if right button is still down (might be released off board!)
5411 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5412 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5413 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5415 lastX = x; lastY = y;
5417 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5418 if(endPV < 0) return;
5419 if(y < margin) step = 1; else
5420 if(y > h - margin) step = -1;
5421 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5422 currentMove += step;
5423 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5424 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5425 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5426 DrawPosition(FALSE, boards[currentMove]);
5430 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5431 // All positions will have equal probability, but the current method will not provide a unique
5432 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5438 int piecesLeft[(int)BlackPawn];
5439 int seed, nrOfShuffles;
5441 void GetPositionNumber()
5442 { // sets global variable seed
5445 seed = appData.defaultFrcPosition;
5446 if(seed < 0) { // randomize based on time for negative FRC position numbers
5447 for(i=0; i<50; i++) seed += random();
5448 seed = random() ^ random() >> 8 ^ random() << 8;
5449 if(seed<0) seed = -seed;
5453 int put(Board board, int pieceType, int rank, int n, int shade)
5454 // put the piece on the (n-1)-th empty squares of the given shade
5458 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5459 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5460 board[rank][i] = (ChessSquare) pieceType;
5461 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5463 piecesLeft[pieceType]--;
5471 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5472 // calculate where the next piece goes, (any empty square), and put it there
5476 i = seed % squaresLeft[shade];
5477 nrOfShuffles *= squaresLeft[shade];
5478 seed /= squaresLeft[shade];
5479 put(board, pieceType, rank, i, shade);
5482 void AddTwoPieces(Board board, int pieceType, int rank)
5483 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5485 int i, n=squaresLeft[ANY], j=n-1, k;
5487 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5488 i = seed % k; // pick one
5491 while(i >= j) i -= j--;
5492 j = n - 1 - j; i += j;
5493 put(board, pieceType, rank, j, ANY);
5494 put(board, pieceType, rank, i, ANY);
5497 void SetUpShuffle(Board board, int number)
5501 GetPositionNumber(); nrOfShuffles = 1;
5503 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5504 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5505 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5507 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5509 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5510 p = (int) board[0][i];
5511 if(p < (int) BlackPawn) piecesLeft[p] ++;
5512 board[0][i] = EmptySquare;
5515 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5516 // shuffles restricted to allow normal castling put KRR first
5517 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5518 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5519 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5520 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5521 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5522 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5523 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5524 put(board, WhiteRook, 0, 0, ANY);
5525 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5528 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5529 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5530 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5531 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5532 while(piecesLeft[p] >= 2) {
5533 AddOnePiece(board, p, 0, LITE);
5534 AddOnePiece(board, p, 0, DARK);
5536 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5539 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5540 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5541 // but we leave King and Rooks for last, to possibly obey FRC restriction
5542 if(p == (int)WhiteRook) continue;
5543 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5544 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5547 // now everything is placed, except perhaps King (Unicorn) and Rooks
5549 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5550 // Last King gets castling rights
5551 while(piecesLeft[(int)WhiteUnicorn]) {
5552 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5553 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5556 while(piecesLeft[(int)WhiteKing]) {
5557 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5558 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5563 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5564 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5567 // Only Rooks can be left; simply place them all
5568 while(piecesLeft[(int)WhiteRook]) {
5569 i = put(board, WhiteRook, 0, 0, ANY);
5570 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5573 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5575 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5578 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5579 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5582 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5585 int SetCharTable( char *table, const char * map )
5586 /* [HGM] moved here from winboard.c because of its general usefulness */
5587 /* Basically a safe strcpy that uses the last character as King */
5589 int result = FALSE; int NrPieces;
5591 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5592 && NrPieces >= 12 && !(NrPieces&1)) {
5593 int i; /* [HGM] Accept even length from 12 to 34 */
5595 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5596 for( i=0; i<NrPieces/2-1; i++ ) {
5598 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5600 table[(int) WhiteKing] = map[NrPieces/2-1];
5601 table[(int) BlackKing] = map[NrPieces-1];
5609 void Prelude(Board board)
5610 { // [HGM] superchess: random selection of exo-pieces
5611 int i, j, k; ChessSquare p;
5612 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5614 GetPositionNumber(); // use FRC position number
5616 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5617 SetCharTable(pieceToChar, appData.pieceToCharTable);
5618 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5619 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5622 j = seed%4; seed /= 4;
5623 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5624 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5625 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5626 j = seed%3 + (seed%3 >= j); seed /= 3;
5627 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5628 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5629 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5630 j = seed%3; seed /= 3;
5631 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5632 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5633 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5634 j = seed%2 + (seed%2 >= j); seed /= 2;
5635 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5636 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5637 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5638 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5639 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5640 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5641 put(board, exoPieces[0], 0, 0, ANY);
5642 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5646 InitPosition(redraw)
5649 ChessSquare (* pieces)[BOARD_FILES];
5650 int i, j, pawnRow, overrule,
5651 oldx = gameInfo.boardWidth,
5652 oldy = gameInfo.boardHeight,
5653 oldh = gameInfo.holdingsWidth;
5656 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5658 /* [AS] Initialize pv info list [HGM] and game status */
5660 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5661 pvInfoList[i].depth = 0;
5662 boards[i][EP_STATUS] = EP_NONE;
5663 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5666 initialRulePlies = 0; /* 50-move counter start */
5668 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5669 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5673 /* [HGM] logic here is completely changed. In stead of full positions */
5674 /* the initialized data only consist of the two backranks. The switch */
5675 /* selects which one we will use, which is than copied to the Board */
5676 /* initialPosition, which for the rest is initialized by Pawns and */
5677 /* empty squares. This initial position is then copied to boards[0], */
5678 /* possibly after shuffling, so that it remains available. */
5680 gameInfo.holdingsWidth = 0; /* default board sizes */
5681 gameInfo.boardWidth = 8;
5682 gameInfo.boardHeight = 8;
5683 gameInfo.holdingsSize = 0;
5684 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5685 for(i=0; i<BOARD_FILES-2; i++)
5686 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5687 initialPosition[EP_STATUS] = EP_NONE;
5688 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5689 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5690 SetCharTable(pieceNickName, appData.pieceNickNames);
5691 else SetCharTable(pieceNickName, "............");
5694 switch (gameInfo.variant) {
5695 case VariantFischeRandom:
5696 shuffleOpenings = TRUE;
5699 case VariantShatranj:
5700 pieces = ShatranjArray;
5701 nrCastlingRights = 0;
5702 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5705 pieces = makrukArray;
5706 nrCastlingRights = 0;
5707 startedFromSetupPosition = TRUE;
5708 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5710 case VariantTwoKings:
5711 pieces = twoKingsArray;
5713 case VariantCapaRandom:
5714 shuffleOpenings = TRUE;
5715 case VariantCapablanca:
5716 pieces = CapablancaArray;
5717 gameInfo.boardWidth = 10;
5718 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5721 pieces = GothicArray;
5722 gameInfo.boardWidth = 10;
5723 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5726 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5727 gameInfo.holdingsSize = 7;
5730 pieces = JanusArray;
5731 gameInfo.boardWidth = 10;
5732 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5733 nrCastlingRights = 6;
5734 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5735 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5736 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5737 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5738 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5739 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5742 pieces = FalconArray;
5743 gameInfo.boardWidth = 10;
5744 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5746 case VariantXiangqi:
5747 pieces = XiangqiArray;
5748 gameInfo.boardWidth = 9;
5749 gameInfo.boardHeight = 10;
5750 nrCastlingRights = 0;
5751 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5754 pieces = ShogiArray;
5755 gameInfo.boardWidth = 9;
5756 gameInfo.boardHeight = 9;
5757 gameInfo.holdingsSize = 7;
5758 nrCastlingRights = 0;
5759 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5761 case VariantCourier:
5762 pieces = CourierArray;
5763 gameInfo.boardWidth = 12;
5764 nrCastlingRights = 0;
5765 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5767 case VariantKnightmate:
5768 pieces = KnightmateArray;
5769 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5771 case VariantSpartan:
5772 pieces = SpartanArray;
5773 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5776 pieces = fairyArray;
5777 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5780 pieces = GreatArray;
5781 gameInfo.boardWidth = 10;
5782 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5783 gameInfo.holdingsSize = 8;
5787 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5788 gameInfo.holdingsSize = 8;
5789 startedFromSetupPosition = TRUE;
5791 case VariantCrazyhouse:
5792 case VariantBughouse:
5794 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5795 gameInfo.holdingsSize = 5;
5797 case VariantWildCastle:
5799 /* !!?shuffle with kings guaranteed to be on d or e file */
5800 shuffleOpenings = 1;
5802 case VariantNoCastle:
5804 nrCastlingRights = 0;
5805 /* !!?unconstrained back-rank shuffle */
5806 shuffleOpenings = 1;
5811 if(appData.NrFiles >= 0) {
5812 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5813 gameInfo.boardWidth = appData.NrFiles;
5815 if(appData.NrRanks >= 0) {
5816 gameInfo.boardHeight = appData.NrRanks;
5818 if(appData.holdingsSize >= 0) {
5819 i = appData.holdingsSize;
5820 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5821 gameInfo.holdingsSize = i;
5823 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5824 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5825 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5827 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5828 if(pawnRow < 1) pawnRow = 1;
5829 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5831 /* User pieceToChar list overrules defaults */
5832 if(appData.pieceToCharTable != NULL)
5833 SetCharTable(pieceToChar, appData.pieceToCharTable);
5835 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5837 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5838 s = (ChessSquare) 0; /* account holding counts in guard band */
5839 for( i=0; i<BOARD_HEIGHT; i++ )
5840 initialPosition[i][j] = s;
5842 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5843 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5844 initialPosition[pawnRow][j] = WhitePawn;
5845 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5846 if(gameInfo.variant == VariantXiangqi) {
5848 initialPosition[pawnRow][j] =
5849 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5850 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5851 initialPosition[2][j] = WhiteCannon;
5852 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5856 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5858 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5861 initialPosition[1][j] = WhiteBishop;
5862 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5864 initialPosition[1][j] = WhiteRook;
5865 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5868 if( nrCastlingRights == -1) {
5869 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5870 /* This sets default castling rights from none to normal corners */
5871 /* Variants with other castling rights must set them themselves above */
5872 nrCastlingRights = 6;
5874 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5875 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5876 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5877 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5878 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5879 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5882 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5883 if(gameInfo.variant == VariantGreat) { // promotion commoners
5884 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5885 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5886 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5887 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5889 if( gameInfo.variant == VariantSChess ) {
5890 initialPosition[1][0] = BlackMarshall;
5891 initialPosition[2][0] = BlackAngel;
5892 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5893 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5894 initialPosition[1][1] = initialPosition[2][1] =
5895 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5897 if (appData.debugMode) {
5898 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5900 if(shuffleOpenings) {
5901 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5902 startedFromSetupPosition = TRUE;
5904 if(startedFromPositionFile) {
5905 /* [HGM] loadPos: use PositionFile for every new game */
5906 CopyBoard(initialPosition, filePosition);
5907 for(i=0; i<nrCastlingRights; i++)
5908 initialRights[i] = filePosition[CASTLING][i];
5909 startedFromSetupPosition = TRUE;
5912 CopyBoard(boards[0], initialPosition);
5914 if(oldx != gameInfo.boardWidth ||
5915 oldy != gameInfo.boardHeight ||
5916 oldv != gameInfo.variant ||
5917 oldh != gameInfo.holdingsWidth
5919 InitDrawingSizes(-2 ,0);
5921 oldv = gameInfo.variant;
5923 DrawPosition(TRUE, boards[currentMove]);
5927 SendBoard(cps, moveNum)
5928 ChessProgramState *cps;
5931 char message[MSG_SIZ];
5933 if (cps->useSetboard) {
5934 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5935 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5936 SendToProgram(message, cps);
5942 /* Kludge to set black to move, avoiding the troublesome and now
5943 * deprecated "black" command.
5945 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5946 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5948 SendToProgram("edit\n", cps);
5949 SendToProgram("#\n", cps);
5950 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5951 bp = &boards[moveNum][i][BOARD_LEFT];
5952 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5953 if ((int) *bp < (int) BlackPawn) {
5954 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5956 if(message[0] == '+' || message[0] == '~') {
5957 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5958 PieceToChar((ChessSquare)(DEMOTED *bp)),
5961 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5962 message[1] = BOARD_RGHT - 1 - j + '1';
5963 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5965 SendToProgram(message, cps);
5970 SendToProgram("c\n", cps);
5971 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5972 bp = &boards[moveNum][i][BOARD_LEFT];
5973 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5974 if (((int) *bp != (int) EmptySquare)
5975 && ((int) *bp >= (int) BlackPawn)) {
5976 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5978 if(message[0] == '+' || message[0] == '~') {
5979 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5980 PieceToChar((ChessSquare)(DEMOTED *bp)),
5983 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5984 message[1] = BOARD_RGHT - 1 - j + '1';
5985 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5987 SendToProgram(message, cps);
5992 SendToProgram(".\n", cps);
5994 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5998 DefaultPromoChoice(int white)
6001 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6002 result = WhiteFerz; // no choice
6003 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6004 result= WhiteKing; // in Suicide Q is the last thing we want
6005 else if(gameInfo.variant == VariantSpartan)
6006 result = white ? WhiteQueen : WhiteAngel;
6007 else result = WhiteQueen;
6008 if(!white) result = WHITE_TO_BLACK result;
6012 static int autoQueen; // [HGM] oneclick
6015 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6017 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6018 /* [HGM] add Shogi promotions */
6019 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6024 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6025 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6027 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6028 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6031 piece = boards[currentMove][fromY][fromX];
6032 if(gameInfo.variant == VariantShogi) {
6033 promotionZoneSize = BOARD_HEIGHT/3;
6034 highestPromotingPiece = (int)WhiteFerz;
6035 } else if(gameInfo.variant == VariantMakruk) {
6036 promotionZoneSize = 3;
6039 // Treat Lance as Pawn when it is not representing Amazon
6040 if(gameInfo.variant != VariantSuper) {
6041 if(piece == WhiteLance) piece = WhitePawn; else
6042 if(piece == BlackLance) piece = BlackPawn;
6045 // next weed out all moves that do not touch the promotion zone at all
6046 if((int)piece >= BlackPawn) {
6047 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6049 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6051 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6052 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6055 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6057 // weed out mandatory Shogi promotions
6058 if(gameInfo.variant == VariantShogi) {
6059 if(piece >= BlackPawn) {
6060 if(toY == 0 && piece == BlackPawn ||
6061 toY == 0 && piece == BlackQueen ||
6062 toY <= 1 && piece == BlackKnight) {
6067 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6068 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6069 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6076 // weed out obviously illegal Pawn moves
6077 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6078 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6079 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6080 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6081 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6082 // note we are not allowed to test for valid (non-)capture, due to premove
6085 // we either have a choice what to promote to, or (in Shogi) whether to promote
6086 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6087 *promoChoice = PieceToChar(BlackFerz); // no choice
6090 // no sense asking what we must promote to if it is going to explode...
6091 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6092 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6095 // give caller the default choice even if we will not make it
6096 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6097 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6098 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6099 && gameInfo.variant != VariantShogi
6100 && gameInfo.variant != VariantSuper) return FALSE;
6101 if(autoQueen) return FALSE; // predetermined
6103 // suppress promotion popup on illegal moves that are not premoves
6104 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6105 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6106 if(appData.testLegality && !premove) {
6107 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6108 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6109 if(moveType != WhitePromotion && moveType != BlackPromotion)
6117 InPalace(row, column)
6119 { /* [HGM] for Xiangqi */
6120 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6121 column < (BOARD_WIDTH + 4)/2 &&
6122 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6127 PieceForSquare (x, y)
6131 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6134 return boards[currentMove][y][x];
6138 OKToStartUserMove(x, y)
6141 ChessSquare from_piece;
6144 if (matchMode) return FALSE;
6145 if (gameMode == EditPosition) return TRUE;
6147 if (x >= 0 && y >= 0)
6148 from_piece = boards[currentMove][y][x];
6150 from_piece = EmptySquare;
6152 if (from_piece == EmptySquare) return FALSE;
6154 white_piece = (int)from_piece >= (int)WhitePawn &&
6155 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6158 case PlayFromGameFile:
6160 case TwoMachinesPlay:
6168 case MachinePlaysWhite:
6169 case IcsPlayingBlack:
6170 if (appData.zippyPlay) return FALSE;
6172 DisplayMoveError(_("You are playing Black"));
6177 case MachinePlaysBlack:
6178 case IcsPlayingWhite:
6179 if (appData.zippyPlay) return FALSE;
6181 DisplayMoveError(_("You are playing White"));
6187 if (!white_piece && WhiteOnMove(currentMove)) {
6188 DisplayMoveError(_("It is White's turn"));
6191 if (white_piece && !WhiteOnMove(currentMove)) {
6192 DisplayMoveError(_("It is Black's turn"));
6195 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6196 /* Editing correspondence game history */
6197 /* Could disallow this or prompt for confirmation */
6202 case BeginningOfGame:
6203 if (appData.icsActive) return FALSE;
6204 if (!appData.noChessProgram) {
6206 DisplayMoveError(_("You are playing White"));
6213 if (!white_piece && WhiteOnMove(currentMove)) {
6214 DisplayMoveError(_("It is White's turn"));
6217 if (white_piece && !WhiteOnMove(currentMove)) {
6218 DisplayMoveError(_("It is Black's turn"));
6227 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6228 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6229 && gameMode != AnalyzeFile && gameMode != Training) {
6230 DisplayMoveError(_("Displayed position is not current"));
6237 OnlyMove(int *x, int *y, Boolean captures) {
6238 DisambiguateClosure cl;
6239 if (appData.zippyPlay) return FALSE;
6241 case MachinePlaysBlack:
6242 case IcsPlayingWhite:
6243 case BeginningOfGame:
6244 if(!WhiteOnMove(currentMove)) return FALSE;
6246 case MachinePlaysWhite:
6247 case IcsPlayingBlack:
6248 if(WhiteOnMove(currentMove)) return FALSE;
6255 cl.pieceIn = EmptySquare;
6260 cl.promoCharIn = NULLCHAR;
6261 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6262 if( cl.kind == NormalMove ||
6263 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6264 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6265 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6272 if(cl.kind != ImpossibleMove) return FALSE;
6273 cl.pieceIn = EmptySquare;
6278 cl.promoCharIn = NULLCHAR;
6279 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6280 if( cl.kind == NormalMove ||
6281 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6282 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6283 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6288 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6294 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6295 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6296 int lastLoadGameUseList = FALSE;
6297 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6298 ChessMove lastLoadGameStart = EndOfFile;
6301 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6302 int fromX, fromY, toX, toY;
6306 ChessSquare pdown, pup;
6308 /* Check if the user is playing in turn. This is complicated because we
6309 let the user "pick up" a piece before it is his turn. So the piece he
6310 tried to pick up may have been captured by the time he puts it down!
6311 Therefore we use the color the user is supposed to be playing in this
6312 test, not the color of the piece that is currently on the starting
6313 square---except in EditGame mode, where the user is playing both
6314 sides; fortunately there the capture race can't happen. (It can
6315 now happen in IcsExamining mode, but that's just too bad. The user
6316 will get a somewhat confusing message in that case.)
6320 case PlayFromGameFile:
6322 case TwoMachinesPlay:
6326 /* We switched into a game mode where moves are not accepted,
6327 perhaps while the mouse button was down. */
6330 case MachinePlaysWhite:
6331 /* User is moving for Black */
6332 if (WhiteOnMove(currentMove)) {
6333 DisplayMoveError(_("It is White's turn"));
6338 case MachinePlaysBlack:
6339 /* User is moving for White */
6340 if (!WhiteOnMove(currentMove)) {
6341 DisplayMoveError(_("It is Black's turn"));
6348 case BeginningOfGame:
6351 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6352 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6353 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6354 /* User is moving for Black */
6355 if (WhiteOnMove(currentMove)) {
6356 DisplayMoveError(_("It is White's turn"));
6360 /* User is moving for White */
6361 if (!WhiteOnMove(currentMove)) {
6362 DisplayMoveError(_("It is Black's turn"));
6368 case IcsPlayingBlack:
6369 /* User is moving for Black */
6370 if (WhiteOnMove(currentMove)) {
6371 if (!appData.premove) {
6372 DisplayMoveError(_("It is White's turn"));
6373 } else if (toX >= 0 && toY >= 0) {
6376 premoveFromX = fromX;
6377 premoveFromY = fromY;
6378 premovePromoChar = promoChar;
6380 if (appData.debugMode)
6381 fprintf(debugFP, "Got premove: fromX %d,"
6382 "fromY %d, toX %d, toY %d\n",
6383 fromX, fromY, toX, toY);
6389 case IcsPlayingWhite:
6390 /* User is moving for White */
6391 if (!WhiteOnMove(currentMove)) {
6392 if (!appData.premove) {
6393 DisplayMoveError(_("It is Black's turn"));
6394 } else if (toX >= 0 && toY >= 0) {
6397 premoveFromX = fromX;
6398 premoveFromY = fromY;
6399 premovePromoChar = promoChar;
6401 if (appData.debugMode)
6402 fprintf(debugFP, "Got premove: fromX %d,"
6403 "fromY %d, toX %d, toY %d\n",
6404 fromX, fromY, toX, toY);
6414 /* EditPosition, empty square, or different color piece;
6415 click-click move is possible */
6416 if (toX == -2 || toY == -2) {
6417 boards[0][fromY][fromX] = EmptySquare;
6418 DrawPosition(FALSE, boards[currentMove]);
6420 } else if (toX >= 0 && toY >= 0) {
6421 boards[0][toY][toX] = boards[0][fromY][fromX];
6422 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6423 if(boards[0][fromY][0] != EmptySquare) {
6424 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6425 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6428 if(fromX == BOARD_RGHT+1) {
6429 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6430 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6431 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6434 boards[0][fromY][fromX] = EmptySquare;
6435 DrawPosition(FALSE, boards[currentMove]);
6441 if(toX < 0 || toY < 0) return;
6442 pdown = boards[currentMove][fromY][fromX];
6443 pup = boards[currentMove][toY][toX];
6445 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6446 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6447 if( pup != EmptySquare ) return;
6448 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6449 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6450 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6451 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6452 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6453 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6454 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6458 /* [HGM] always test for legality, to get promotion info */
6459 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6460 fromY, fromX, toY, toX, promoChar);
6461 /* [HGM] but possibly ignore an IllegalMove result */
6462 if (appData.testLegality) {
6463 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6464 DisplayMoveError(_("Illegal move"));
6469 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6472 /* Common tail of UserMoveEvent and DropMenuEvent */
6474 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6476 int fromX, fromY, toX, toY;
6477 /*char*/int promoChar;
6481 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6482 // [HGM] superchess: suppress promotions to non-available piece
6483 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6484 if(WhiteOnMove(currentMove)) {
6485 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6487 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6491 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6492 move type in caller when we know the move is a legal promotion */
6493 if(moveType == NormalMove && promoChar)
6494 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6496 /* [HGM] <popupFix> The following if has been moved here from
6497 UserMoveEvent(). Because it seemed to belong here (why not allow
6498 piece drops in training games?), and because it can only be
6499 performed after it is known to what we promote. */
6500 if (gameMode == Training) {
6501 /* compare the move played on the board to the next move in the
6502 * game. If they match, display the move and the opponent's response.
6503 * If they don't match, display an error message.
6507 CopyBoard(testBoard, boards[currentMove]);
6508 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6510 if (CompareBoards(testBoard, boards[currentMove+1])) {
6511 ForwardInner(currentMove+1);
6513 /* Autoplay the opponent's response.
6514 * if appData.animate was TRUE when Training mode was entered,
6515 * the response will be animated.
6517 saveAnimate = appData.animate;
6518 appData.animate = animateTraining;
6519 ForwardInner(currentMove+1);
6520 appData.animate = saveAnimate;
6522 /* check for the end of the game */
6523 if (currentMove >= forwardMostMove) {
6524 gameMode = PlayFromGameFile;
6526 SetTrainingModeOff();
6527 DisplayInformation(_("End of game"));
6530 DisplayError(_("Incorrect move"), 0);
6535 /* Ok, now we know that the move is good, so we can kill
6536 the previous line in Analysis Mode */
6537 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6538 && currentMove < forwardMostMove) {
6539 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6540 else forwardMostMove = currentMove;
6543 /* If we need the chess program but it's dead, restart it */
6544 ResurrectChessProgram();
6546 /* A user move restarts a paused game*/
6550 thinkOutput[0] = NULLCHAR;
6552 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6554 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6555 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6559 if (gameMode == BeginningOfGame) {
6560 if (appData.noChessProgram) {
6561 gameMode = EditGame;
6565 gameMode = MachinePlaysBlack;
6568 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6570 if (first.sendName) {
6571 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6572 SendToProgram(buf, &first);
6579 /* Relay move to ICS or chess engine */
6580 if (appData.icsActive) {
6581 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6582 gameMode == IcsExamining) {
6583 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6584 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6586 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6588 // also send plain move, in case ICS does not understand atomic claims
6589 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6593 if (first.sendTime && (gameMode == BeginningOfGame ||
6594 gameMode == MachinePlaysWhite ||
6595 gameMode == MachinePlaysBlack)) {
6596 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6598 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6599 // [HGM] book: if program might be playing, let it use book
6600 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6601 first.maybeThinking = TRUE;
6602 } else SendMoveToProgram(forwardMostMove-1, &first);
6603 if (currentMove == cmailOldMove + 1) {
6604 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6608 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612 if(appData.testLegality)
6613 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6619 if (WhiteOnMove(currentMove)) {
6620 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6622 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6626 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6631 case MachinePlaysBlack:
6632 case MachinePlaysWhite:
6633 /* disable certain menu options while machine is thinking */
6634 SetMachineThinkingEnables();
6641 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6642 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6644 if(bookHit) { // [HGM] book: simulate book reply
6645 static char bookMove[MSG_SIZ]; // a bit generous?
6647 programStats.nodes = programStats.depth = programStats.time =
6648 programStats.score = programStats.got_only_move = 0;
6649 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6651 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6652 strcat(bookMove, bookHit);
6653 HandleMachineMove(bookMove, &first);
6659 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6666 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6667 Markers *m = (Markers *) closure;
6668 if(rf == fromY && ff == fromX)
6669 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6670 || kind == WhiteCapturesEnPassant
6671 || kind == BlackCapturesEnPassant);
6672 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6676 MarkTargetSquares(int clear)
6679 if(!appData.markers || !appData.highlightDragging ||
6680 !appData.testLegality || gameMode == EditPosition) return;
6682 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6685 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6686 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6687 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6689 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6692 DrawPosition(TRUE, NULL);
6696 Explode(Board board, int fromX, int fromY, int toX, int toY)
6698 if(gameInfo.variant == VariantAtomic &&
6699 (board[toY][toX] != EmptySquare || // capture?
6700 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6701 board[fromY][fromX] == BlackPawn )
6703 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6709 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6711 int CanPromote(ChessSquare piece, int y)
6713 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6714 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6715 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6716 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6717 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6718 gameInfo.variant == VariantMakruk) return FALSE;
6719 return (piece == BlackPawn && y == 1 ||
6720 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6721 piece == BlackLance && y == 1 ||
6722 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6725 void LeftClick(ClickType clickType, int xPix, int yPix)
6728 Boolean saveAnimate;
6729 static int second = 0, promotionChoice = 0, clearFlag = 0;
6730 char promoChoice = NULLCHAR;
6733 if(appData.seekGraph && appData.icsActive && loggedOn &&
6734 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6735 SeekGraphClick(clickType, xPix, yPix, 0);
6739 if (clickType == Press) ErrorPopDown();
6740 MarkTargetSquares(1);
6742 x = EventToSquare(xPix, BOARD_WIDTH);
6743 y = EventToSquare(yPix, BOARD_HEIGHT);
6744 if (!flipView && y >= 0) {
6745 y = BOARD_HEIGHT - 1 - y;
6747 if (flipView && x >= 0) {
6748 x = BOARD_WIDTH - 1 - x;
6751 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6752 defaultPromoChoice = promoSweep;
6753 promoSweep = EmptySquare; // terminate sweep
6754 promoDefaultAltered = TRUE;
6755 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6758 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6759 if(clickType == Release) return; // ignore upclick of click-click destination
6760 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6761 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6762 if(gameInfo.holdingsWidth &&
6763 (WhiteOnMove(currentMove)
6764 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6765 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6766 // click in right holdings, for determining promotion piece
6767 ChessSquare p = boards[currentMove][y][x];
6768 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6769 if(p != EmptySquare) {
6770 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6775 DrawPosition(FALSE, boards[currentMove]);
6779 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6780 if(clickType == Press
6781 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6782 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6783 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6786 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6787 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6789 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6790 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6791 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6792 defaultPromoChoice = DefaultPromoChoice(side);
6795 autoQueen = appData.alwaysPromoteToQueen;
6799 gatingPiece = EmptySquare;
6800 if (clickType != Press) {
6801 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6802 DragPieceEnd(xPix, yPix); dragging = 0;
6803 DrawPosition(FALSE, NULL);
6807 fromX = x; fromY = y;
6808 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6809 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6810 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6812 if (OKToStartUserMove(fromX, fromY)) {
6814 MarkTargetSquares(0);
6815 DragPieceBegin(xPix, yPix); dragging = 1;
6816 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6817 promoSweep = defaultPromoChoice;
6818 selectFlag = 0; lastX = xPix; lastY = yPix;
6819 Sweep(0); // Pawn that is going to promote: preview promotion piece
6820 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6822 if (appData.highlightDragging) {
6823 SetHighlights(fromX, fromY, -1, -1);
6825 } else fromX = fromY = -1;
6831 if (clickType == Press && gameMode != EditPosition) {
6836 // ignore off-board to clicks
6837 if(y < 0 || x < 0) return;
6839 /* Check if clicking again on the same color piece */
6840 fromP = boards[currentMove][fromY][fromX];
6841 toP = boards[currentMove][y][x];
6842 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6843 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6844 WhitePawn <= toP && toP <= WhiteKing &&
6845 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6846 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6847 (BlackPawn <= fromP && fromP <= BlackKing &&
6848 BlackPawn <= toP && toP <= BlackKing &&
6849 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6850 !(fromP == BlackKing && toP == BlackRook && frc))) {
6851 /* Clicked again on same color piece -- changed his mind */
6852 second = (x == fromX && y == fromY);
6853 promoDefaultAltered = FALSE;
6854 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6855 if (appData.highlightDragging) {
6856 SetHighlights(x, y, -1, -1);
6860 if (OKToStartUserMove(x, y)) {
6861 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6862 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6863 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6864 gatingPiece = boards[currentMove][fromY][fromX];
6865 else gatingPiece = EmptySquare;
6867 fromY = y; dragging = 1;
6868 MarkTargetSquares(0);
6869 DragPieceBegin(xPix, yPix);
6870 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6871 promoSweep = defaultPromoChoice;
6872 selectFlag = 0; lastX = xPix; lastY = yPix;
6873 Sweep(0); // Pawn that is going to promote: preview promotion piece
6877 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6880 // ignore clicks on holdings
6881 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6884 if (clickType == Release && x == fromX && y == fromY) {
6885 DragPieceEnd(xPix, yPix); dragging = 0;
6887 // a deferred attempt to click-click move an empty square on top of a piece
6888 boards[currentMove][y][x] = EmptySquare;
6890 DrawPosition(FALSE, boards[currentMove]);
6891 fromX = fromY = -1; clearFlag = 0;
6894 if (appData.animateDragging) {
6895 /* Undo animation damage if any */
6896 DrawPosition(FALSE, NULL);
6899 /* Second up/down in same square; just abort move */
6902 gatingPiece = EmptySquare;
6905 ClearPremoveHighlights();
6907 /* First upclick in same square; start click-click mode */
6908 SetHighlights(x, y, -1, -1);
6915 /* we now have a different from- and (possibly off-board) to-square */
6916 /* Completed move */
6919 saveAnimate = appData.animate;
6920 if (clickType == Press) {
6921 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6922 // must be Edit Position mode with empty-square selected
6923 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6924 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6927 /* Finish clickclick move */
6928 if (appData.animate || appData.highlightLastMove) {
6929 SetHighlights(fromX, fromY, toX, toY);
6934 /* Finish drag move */
6935 if (appData.highlightLastMove) {
6936 SetHighlights(fromX, fromY, toX, toY);
6940 DragPieceEnd(xPix, yPix); dragging = 0;
6941 /* Don't animate move and drag both */
6942 appData.animate = FALSE;
6945 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6946 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6947 ChessSquare piece = boards[currentMove][fromY][fromX];
6948 if(gameMode == EditPosition && piece != EmptySquare &&
6949 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6952 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6953 n = PieceToNumber(piece - (int)BlackPawn);
6954 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6955 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6956 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6958 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6959 n = PieceToNumber(piece);
6960 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6961 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6962 boards[currentMove][n][BOARD_WIDTH-2]++;
6964 boards[currentMove][fromY][fromX] = EmptySquare;
6968 DrawPosition(TRUE, boards[currentMove]);
6972 // off-board moves should not be highlighted
6973 if(x < 0 || y < 0) ClearHighlights();
6975 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6977 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6978 SetHighlights(fromX, fromY, toX, toY);
6979 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6980 // [HGM] super: promotion to captured piece selected from holdings
6981 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6982 promotionChoice = TRUE;
6983 // kludge follows to temporarily execute move on display, without promoting yet
6984 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6985 boards[currentMove][toY][toX] = p;
6986 DrawPosition(FALSE, boards[currentMove]);
6987 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6988 boards[currentMove][toY][toX] = q;
6989 DisplayMessage("Click in holdings to choose piece", "");
6994 int oldMove = currentMove;
6995 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6996 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6997 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6998 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6999 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7000 DrawPosition(TRUE, boards[currentMove]);
7003 appData.animate = saveAnimate;
7004 if (appData.animate || appData.animateDragging) {
7005 /* Undo animation damage if needed */
7006 DrawPosition(FALSE, NULL);
7010 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7011 { // front-end-free part taken out of PieceMenuPopup
7012 int whichMenu; int xSqr, ySqr;
7014 if(seekGraphUp) { // [HGM] seekgraph
7015 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7016 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7020 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7021 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7022 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7023 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7024 if(action == Press) {
7025 originalFlip = flipView;
7026 flipView = !flipView; // temporarily flip board to see game from partners perspective
7027 DrawPosition(TRUE, partnerBoard);
7028 DisplayMessage(partnerStatus, "");
7030 } else if(action == Release) {
7031 flipView = originalFlip;
7032 DrawPosition(TRUE, boards[currentMove]);
7038 xSqr = EventToSquare(x, BOARD_WIDTH);
7039 ySqr = EventToSquare(y, BOARD_HEIGHT);
7040 if (action == Release) {
7041 if(pieceSweep != EmptySquare) {
7042 EditPositionMenuEvent(pieceSweep, toX, toY);
7043 pieceSweep = EmptySquare;
7044 } else UnLoadPV(); // [HGM] pv
7046 if (action != Press) return -2; // return code to be ignored
7049 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7051 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7052 if (xSqr < 0 || ySqr < 0) return -1;
7053 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7054 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7055 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7056 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7060 if(!appData.icsEngineAnalyze) return -1;
7061 case IcsPlayingWhite:
7062 case IcsPlayingBlack:
7063 if(!appData.zippyPlay) goto noZip;
7066 case MachinePlaysWhite:
7067 case MachinePlaysBlack:
7068 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7069 if (!appData.dropMenu) {
7071 return 2; // flag front-end to grab mouse events
7073 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7074 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7077 if (xSqr < 0 || ySqr < 0) return -1;
7078 if (!appData.dropMenu || appData.testLegality &&
7079 gameInfo.variant != VariantBughouse &&
7080 gameInfo.variant != VariantCrazyhouse) return -1;
7081 whichMenu = 1; // drop menu
7087 if (((*fromX = xSqr) < 0) ||
7088 ((*fromY = ySqr) < 0)) {
7089 *fromX = *fromY = -1;
7093 *fromX = BOARD_WIDTH - 1 - *fromX;
7095 *fromY = BOARD_HEIGHT - 1 - *fromY;
7100 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7102 // char * hint = lastHint;
7103 FrontEndProgramStats stats;
7105 stats.which = cps == &first ? 0 : 1;
7106 stats.depth = cpstats->depth;
7107 stats.nodes = cpstats->nodes;
7108 stats.score = cpstats->score;
7109 stats.time = cpstats->time;
7110 stats.pv = cpstats->movelist;
7111 stats.hint = lastHint;
7112 stats.an_move_index = 0;
7113 stats.an_move_count = 0;
7115 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7116 stats.hint = cpstats->move_name;
7117 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7118 stats.an_move_count = cpstats->nr_moves;
7121 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
7123 SetProgramStats( &stats );
7126 #define MAXPLAYERS 500
7129 TourneyStandings(int display)
7131 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7132 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7133 char result, *p, *names[MAXPLAYERS];
7135 names[0] = p = strdup(appData.participants);
7136 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7138 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7140 while(result = appData.results[nr]) {
7141 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7142 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7143 wScore = bScore = 0;
7145 case '+': wScore = 2; break;
7146 case '-': bScore = 2; break;
7147 case '=': wScore = bScore = 1; break;
7149 case '*': return strdup("busy"); // tourney not finished
7157 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7158 for(w=0; w<nPlayers; w++) {
7160 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7161 ranking[w] = b; points[w] = bScore; score[b] = -2;
7163 p = malloc(nPlayers*34+1);
7164 for(w=0; w<nPlayers && w<display; w++)
7165 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7171 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7172 { // count all piece types
7174 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7175 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7176 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7179 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7180 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7181 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7182 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7183 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7184 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7189 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7191 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7192 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7194 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7195 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7196 if(myPawns == 2 && nMine == 3) // KPP
7197 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7198 if(myPawns == 1 && nMine == 2) // KP
7199 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7200 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7201 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7202 if(myPawns) return FALSE;
7203 if(pCnt[WhiteRook+side])
7204 return pCnt[BlackRook-side] ||
7205 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7206 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7207 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7208 if(pCnt[WhiteCannon+side]) {
7209 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7210 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7212 if(pCnt[WhiteKnight+side])
7213 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7218 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7220 VariantClass v = gameInfo.variant;
7222 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7223 if(v == VariantShatranj) return TRUE; // always winnable through baring
7224 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7225 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7227 if(v == VariantXiangqi) {
7228 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7230 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7231 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7232 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7233 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7234 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7235 if(stale) // we have at least one last-rank P plus perhaps C
7236 return majors // KPKX
7237 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7239 return pCnt[WhiteFerz+side] // KCAK
7240 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7241 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7242 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7244 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7245 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7247 if(nMine == 1) return FALSE; // bare King
7248 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
7249 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7250 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7251 // by now we have King + 1 piece (or multiple Bishops on the same color)
7252 if(pCnt[WhiteKnight+side])
7253 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7254 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7255 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7257 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7258 if(pCnt[WhiteAlfil+side])
7259 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7260 if(pCnt[WhiteWazir+side])
7261 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7268 Adjudicate(ChessProgramState *cps)
7269 { // [HGM] some adjudications useful with buggy engines
7270 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7271 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7272 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7273 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7274 int k, count = 0; static int bare = 1;
7275 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7276 Boolean canAdjudicate = !appData.icsActive;
7278 // most tests only when we understand the game, i.e. legality-checking on
7279 if( appData.testLegality )
7280 { /* [HGM] Some more adjudications for obstinate engines */
7281 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7282 static int moveCount = 6;
7284 char *reason = NULL;
7286 /* Count what is on board. */
7287 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7289 /* Some material-based adjudications that have to be made before stalemate test */
7290 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7291 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7292 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7293 if(canAdjudicate && appData.checkMates) {
7295 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7296 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7297 "Xboard adjudication: King destroyed", GE_XBOARD );
7302 /* Bare King in Shatranj (loses) or Losers (wins) */
7303 if( nrW == 1 || nrB == 1) {
7304 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7305 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7306 if(canAdjudicate && appData.checkMates) {
7308 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7309 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7310 "Xboard adjudication: Bare king", GE_XBOARD );
7314 if( gameInfo.variant == VariantShatranj && --bare < 0)
7316 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7317 if(canAdjudicate && appData.checkMates) {
7318 /* but only adjudicate if adjudication enabled */
7320 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7321 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7322 "Xboard adjudication: Bare king", GE_XBOARD );
7329 // don't wait for engine to announce game end if we can judge ourselves
7330 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7332 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7333 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7334 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7335 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7338 reason = "Xboard adjudication: 3rd check";
7339 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7349 reason = "Xboard adjudication: Stalemate";
7350 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7351 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7352 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7353 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7354 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7355 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7356 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7357 EP_CHECKMATE : EP_WINS);
7358 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7359 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7363 reason = "Xboard adjudication: Checkmate";
7364 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7368 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7370 result = GameIsDrawn; break;
7372 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7374 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7378 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7380 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7381 GameEnds( result, reason, GE_XBOARD );
7385 /* Next absolutely insufficient mating material. */
7386 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7387 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7388 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7390 /* always flag draws, for judging claims */
7391 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7393 if(canAdjudicate && appData.materialDraws) {
7394 /* but only adjudicate them if adjudication enabled */
7395 if(engineOpponent) {
7396 SendToProgram("force\n", engineOpponent); // suppress reply
7397 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7399 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7404 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7405 if(gameInfo.variant == VariantXiangqi ?
7406 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7408 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7409 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7410 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7411 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7413 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7414 { /* if the first 3 moves do not show a tactical win, declare draw */
7415 if(engineOpponent) {
7416 SendToProgram("force\n", engineOpponent); // suppress reply
7417 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7419 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7422 } else moveCount = 6;
7424 if (appData.debugMode) { int i;
7425 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7426 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7427 appData.drawRepeats);
7428 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7429 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7433 // Repetition draws and 50-move rule can be applied independently of legality testing
7435 /* Check for rep-draws */
7437 for(k = forwardMostMove-2;
7438 k>=backwardMostMove && k>=forwardMostMove-100 &&
7439 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7440 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7443 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7444 /* compare castling rights */
7445 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7446 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7447 rights++; /* King lost rights, while rook still had them */
7448 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7449 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7450 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7451 rights++; /* but at least one rook lost them */
7453 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7454 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7456 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7457 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7458 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7461 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7462 && appData.drawRepeats > 1) {
7463 /* adjudicate after user-specified nr of repeats */
7464 int result = GameIsDrawn;
7465 char *details = "XBoard adjudication: repetition draw";
7466 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7467 // [HGM] xiangqi: check for forbidden perpetuals
7468 int m, ourPerpetual = 1, hisPerpetual = 1;
7469 for(m=forwardMostMove; m>k; m-=2) {
7470 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7471 ourPerpetual = 0; // the current mover did not always check
7472 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7473 hisPerpetual = 0; // the opponent did not always check
7475 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7476 ourPerpetual, hisPerpetual);
7477 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7478 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7479 details = "Xboard adjudication: perpetual checking";
7481 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7482 break; // (or we would have caught him before). Abort repetition-checking loop.
7484 // Now check for perpetual chases
7485 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7486 hisPerpetual = PerpetualChase(k, forwardMostMove);
7487 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7488 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7489 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7490 details = "Xboard adjudication: perpetual chasing";
7492 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7493 break; // Abort repetition-checking loop.
7495 // if neither of us is checking or chasing all the time, or both are, it is draw
7497 if(engineOpponent) {
7498 SendToProgram("force\n", engineOpponent); // suppress reply
7499 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7501 GameEnds( result, details, GE_XBOARD );
7504 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7505 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7509 /* Now we test for 50-move draws. Determine ply count */
7510 count = forwardMostMove;
7511 /* look for last irreversble move */
7512 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7514 /* if we hit starting position, add initial plies */
7515 if( count == backwardMostMove )
7516 count -= initialRulePlies;
7517 count = forwardMostMove - count;
7518 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7519 // adjust reversible move counter for checks in Xiangqi
7520 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7521 if(i < backwardMostMove) i = backwardMostMove;
7522 while(i <= forwardMostMove) {
7523 lastCheck = inCheck; // check evasion does not count
7524 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7525 if(inCheck || lastCheck) count--; // check does not count
7530 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7531 /* this is used to judge if draw claims are legal */
7532 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7533 if(engineOpponent) {
7534 SendToProgram("force\n", engineOpponent); // suppress reply
7535 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7537 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7541 /* if draw offer is pending, treat it as a draw claim
7542 * when draw condition present, to allow engines a way to
7543 * claim draws before making their move to avoid a race
7544 * condition occurring after their move
7546 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7548 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7549 p = "Draw claim: 50-move rule";
7550 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7551 p = "Draw claim: 3-fold repetition";
7552 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7553 p = "Draw claim: insufficient mating material";
7554 if( p != NULL && canAdjudicate) {
7555 if(engineOpponent) {
7556 SendToProgram("force\n", engineOpponent); // suppress reply
7557 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7559 GameEnds( GameIsDrawn, p, GE_XBOARD );
7564 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7565 if(engineOpponent) {
7566 SendToProgram("force\n", engineOpponent); // suppress reply
7567 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7569 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7575 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7576 { // [HGM] book: this routine intercepts moves to simulate book replies
7577 char *bookHit = NULL;
7579 //first determine if the incoming move brings opponent into his book
7580 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7581 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7582 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7583 if(bookHit != NULL && !cps->bookSuspend) {
7584 // make sure opponent is not going to reply after receiving move to book position
7585 SendToProgram("force\n", cps);
7586 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7588 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7589 // now arrange restart after book miss
7591 // after a book hit we never send 'go', and the code after the call to this routine
7592 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7593 char buf[MSG_SIZ], *move = bookHit;
7595 int fromX, fromY, toX, toY;
7599 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7600 &fromX, &fromY, &toX, &toY, &promoChar)) {
7601 (void) CoordsToAlgebraic(boards[forwardMostMove],
7602 PosFlags(forwardMostMove),
7603 fromY, fromX, toY, toX, promoChar, move);
7605 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7609 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7610 SendToProgram(buf, cps);
7611 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7612 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7613 SendToProgram("go\n", cps);
7614 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7615 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7616 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7617 SendToProgram("go\n", cps);
7618 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7620 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7624 ChessProgramState *savedState;
7625 void DeferredBookMove(void)
7627 if(savedState->lastPing != savedState->lastPong)
7628 ScheduleDelayedEvent(DeferredBookMove, 10);
7630 HandleMachineMove(savedMessage, savedState);
7634 HandleMachineMove(message, cps)
7636 ChessProgramState *cps;
7638 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7639 char realname[MSG_SIZ];
7640 int fromX, fromY, toX, toY;
7649 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7651 * Kludge to ignore BEL characters
7653 while (*message == '\007') message++;
7656 * [HGM] engine debug message: ignore lines starting with '#' character
7658 if(cps->debug && *message == '#') return;
7661 * Look for book output
7663 if (cps == &first && bookRequested) {
7664 if (message[0] == '\t' || message[0] == ' ') {
7665 /* Part of the book output is here; append it */
7666 strcat(bookOutput, message);
7667 strcat(bookOutput, " \n");
7669 } else if (bookOutput[0] != NULLCHAR) {
7670 /* All of book output has arrived; display it */
7671 char *p = bookOutput;
7672 while (*p != NULLCHAR) {
7673 if (*p == '\t') *p = ' ';
7676 DisplayInformation(bookOutput);
7677 bookRequested = FALSE;
7678 /* Fall through to parse the current output */
7683 * Look for machine move.
7685 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7686 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7688 /* This method is only useful on engines that support ping */
7689 if (cps->lastPing != cps->lastPong) {
7690 if (gameMode == BeginningOfGame) {
7691 /* Extra move from before last new; ignore */
7692 if (appData.debugMode) {
7693 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7696 if (appData.debugMode) {
7697 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7698 cps->which, gameMode);
7701 SendToProgram("undo\n", cps);
7707 case BeginningOfGame:
7708 /* Extra move from before last reset; ignore */
7709 if (appData.debugMode) {
7710 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7717 /* Extra move after we tried to stop. The mode test is
7718 not a reliable way of detecting this problem, but it's
7719 the best we can do on engines that don't support ping.
7721 if (appData.debugMode) {
7722 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7723 cps->which, gameMode);
7725 SendToProgram("undo\n", cps);
7728 case MachinePlaysWhite:
7729 case IcsPlayingWhite:
7730 machineWhite = TRUE;
7733 case MachinePlaysBlack:
7734 case IcsPlayingBlack:
7735 machineWhite = FALSE;
7738 case TwoMachinesPlay:
7739 machineWhite = (cps->twoMachinesColor[0] == 'w');
7742 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7743 if (appData.debugMode) {
7745 "Ignoring move out of turn by %s, gameMode %d"
7746 ", forwardMost %d\n",
7747 cps->which, gameMode, forwardMostMove);
7752 if (appData.debugMode) { int f = forwardMostMove;
7753 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7754 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7755 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7757 if(cps->alphaRank) AlphaRank(machineMove, 4);
7758 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7759 &fromX, &fromY, &toX, &toY, &promoChar)) {
7760 /* Machine move could not be parsed; ignore it. */
7761 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7762 machineMove, _(cps->which));
7763 DisplayError(buf1, 0);
7764 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7765 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7766 if (gameMode == TwoMachinesPlay) {
7767 GameEnds(machineWhite ? BlackWins : WhiteWins,
7773 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7774 /* So we have to redo legality test with true e.p. status here, */
7775 /* to make sure an illegal e.p. capture does not slip through, */
7776 /* to cause a forfeit on a justified illegal-move complaint */
7777 /* of the opponent. */
7778 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7780 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7781 fromY, fromX, toY, toX, promoChar);
7782 if (appData.debugMode) {
7784 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7785 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7786 fprintf(debugFP, "castling rights\n");
7788 if(moveType == IllegalMove) {
7789 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7790 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7791 GameEnds(machineWhite ? BlackWins : WhiteWins,
7794 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7795 /* [HGM] Kludge to handle engines that send FRC-style castling
7796 when they shouldn't (like TSCP-Gothic) */
7798 case WhiteASideCastleFR:
7799 case BlackASideCastleFR:
7801 currentMoveString[2]++;
7803 case WhiteHSideCastleFR:
7804 case BlackHSideCastleFR:
7806 currentMoveString[2]--;
7808 default: ; // nothing to do, but suppresses warning of pedantic compilers
7811 hintRequested = FALSE;
7812 lastHint[0] = NULLCHAR;
7813 bookRequested = FALSE;
7814 /* Program may be pondering now */
7815 cps->maybeThinking = TRUE;
7816 if (cps->sendTime == 2) cps->sendTime = 1;
7817 if (cps->offeredDraw) cps->offeredDraw--;
7819 /* [AS] Save move info*/
7820 pvInfoList[ forwardMostMove ].score = programStats.score;
7821 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7822 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7824 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7826 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7827 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7830 while( count < adjudicateLossPlies ) {
7831 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7834 score = -score; /* Flip score for winning side */
7837 if( score > adjudicateLossThreshold ) {
7844 if( count >= adjudicateLossPlies ) {
7845 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7847 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7848 "Xboard adjudication",
7855 if(Adjudicate(cps)) {
7856 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7857 return; // [HGM] adjudicate: for all automatic game ends
7861 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7863 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7864 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7866 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7868 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7870 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7871 char buf[3*MSG_SIZ];
7873 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7874 programStats.score / 100.,
7876 programStats.time / 100.,
7877 (unsigned int)programStats.nodes,
7878 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7879 programStats.movelist);
7881 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7886 /* [AS] Clear stats for next move */
7887 ClearProgramStats();
7888 thinkOutput[0] = NULLCHAR;
7889 hiddenThinkOutputState = 0;
7892 if (gameMode == TwoMachinesPlay) {
7893 /* [HGM] relaying draw offers moved to after reception of move */
7894 /* and interpreting offer as claim if it brings draw condition */
7895 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7896 SendToProgram("draw\n", cps->other);
7898 if (cps->other->sendTime) {
7899 SendTimeRemaining(cps->other,
7900 cps->other->twoMachinesColor[0] == 'w');
7902 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7903 if (firstMove && !bookHit) {
7905 if (cps->other->useColors) {
7906 SendToProgram(cps->other->twoMachinesColor, cps->other);
7908 SendToProgram("go\n", cps->other);
7910 cps->other->maybeThinking = TRUE;
7913 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7915 if (!pausing && appData.ringBellAfterMoves) {
7920 * Reenable menu items that were disabled while
7921 * machine was thinking
7923 if (gameMode != TwoMachinesPlay)
7924 SetUserThinkingEnables();
7926 // [HGM] book: after book hit opponent has received move and is now in force mode
7927 // force the book reply into it, and then fake that it outputted this move by jumping
7928 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7930 static char bookMove[MSG_SIZ]; // a bit generous?
7932 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7933 strcat(bookMove, bookHit);
7936 programStats.nodes = programStats.depth = programStats.time =
7937 programStats.score = programStats.got_only_move = 0;
7938 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7940 if(cps->lastPing != cps->lastPong) {
7941 savedMessage = message; // args for deferred call
7943 ScheduleDelayedEvent(DeferredBookMove, 10);
7952 /* Set special modes for chess engines. Later something general
7953 * could be added here; for now there is just one kludge feature,
7954 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7955 * when "xboard" is given as an interactive command.
7957 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7958 cps->useSigint = FALSE;
7959 cps->useSigterm = FALSE;
7961 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7962 ParseFeatures(message+8, cps);
7963 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7966 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7967 int dummy, s=6; char buf[MSG_SIZ];
7968 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7969 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7970 ParseFEN(boards[0], &dummy, message+s);
7971 DrawPosition(TRUE, boards[0]);
7972 startedFromSetupPosition = TRUE;
7975 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7976 * want this, I was asked to put it in, and obliged.
7978 if (!strncmp(message, "setboard ", 9)) {
7979 Board initial_position;
7981 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7983 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7984 DisplayError(_("Bad FEN received from engine"), 0);
7988 CopyBoard(boards[0], initial_position);
7989 initialRulePlies = FENrulePlies;
7990 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7991 else gameMode = MachinePlaysBlack;
7992 DrawPosition(FALSE, boards[currentMove]);
7998 * Look for communication commands
8000 if (!strncmp(message, "telluser ", 9)) {
8001 if(message[9] == '\\' && message[10] == '\\')
8002 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8003 DisplayNote(message + 9);
8006 if (!strncmp(message, "tellusererror ", 14)) {
8008 if(message[14] == '\\' && message[15] == '\\')
8009 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8010 DisplayError(message + 14, 0);
8013 if (!strncmp(message, "tellopponent ", 13)) {
8014 if (appData.icsActive) {
8016 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8020 DisplayNote(message + 13);
8024 if (!strncmp(message, "tellothers ", 11)) {
8025 if (appData.icsActive) {
8027 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8033 if (!strncmp(message, "tellall ", 8)) {
8034 if (appData.icsActive) {
8036 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8040 DisplayNote(message + 8);
8044 if (strncmp(message, "warning", 7) == 0) {
8045 /* Undocumented feature, use tellusererror in new code */
8046 DisplayError(message, 0);
8049 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8050 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8051 strcat(realname, " query");
8052 AskQuestion(realname, buf2, buf1, cps->pr);
8055 /* Commands from the engine directly to ICS. We don't allow these to be
8056 * sent until we are logged on. Crafty kibitzes have been known to
8057 * interfere with the login process.
8060 if (!strncmp(message, "tellics ", 8)) {
8061 SendToICS(message + 8);
8065 if (!strncmp(message, "tellicsnoalias ", 15)) {
8066 SendToICS(ics_prefix);
8067 SendToICS(message + 15);
8071 /* The following are for backward compatibility only */
8072 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8073 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8074 SendToICS(ics_prefix);
8080 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8084 * If the move is illegal, cancel it and redraw the board.
8085 * Also deal with other error cases. Matching is rather loose
8086 * here to accommodate engines written before the spec.
8088 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8089 strncmp(message, "Error", 5) == 0) {
8090 if (StrStr(message, "name") ||
8091 StrStr(message, "rating") || StrStr(message, "?") ||
8092 StrStr(message, "result") || StrStr(message, "board") ||
8093 StrStr(message, "bk") || StrStr(message, "computer") ||
8094 StrStr(message, "variant") || StrStr(message, "hint") ||
8095 StrStr(message, "random") || StrStr(message, "depth") ||
8096 StrStr(message, "accepted")) {
8099 if (StrStr(message, "protover")) {
8100 /* Program is responding to input, so it's apparently done
8101 initializing, and this error message indicates it is
8102 protocol version 1. So we don't need to wait any longer
8103 for it to initialize and send feature commands. */
8104 FeatureDone(cps, 1);
8105 cps->protocolVersion = 1;
8108 cps->maybeThinking = FALSE;
8110 if (StrStr(message, "draw")) {
8111 /* Program doesn't have "draw" command */
8112 cps->sendDrawOffers = 0;
8115 if (cps->sendTime != 1 &&
8116 (StrStr(message, "time") || StrStr(message, "otim"))) {
8117 /* Program apparently doesn't have "time" or "otim" command */
8121 if (StrStr(message, "analyze")) {
8122 cps->analysisSupport = FALSE;
8123 cps->analyzing = FALSE;
8125 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8126 DisplayError(buf2, 0);
8129 if (StrStr(message, "(no matching move)st")) {
8130 /* Special kludge for GNU Chess 4 only */
8131 cps->stKludge = TRUE;
8132 SendTimeControl(cps, movesPerSession, timeControl,
8133 timeIncrement, appData.searchDepth,
8137 if (StrStr(message, "(no matching move)sd")) {
8138 /* Special kludge for GNU Chess 4 only */
8139 cps->sdKludge = TRUE;
8140 SendTimeControl(cps, movesPerSession, timeControl,
8141 timeIncrement, appData.searchDepth,
8145 if (!StrStr(message, "llegal")) {
8148 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8149 gameMode == IcsIdle) return;
8150 if (forwardMostMove <= backwardMostMove) return;
8151 if (pausing) PauseEvent();
8152 if(appData.forceIllegal) {
8153 // [HGM] illegal: machine refused move; force position after move into it
8154 SendToProgram("force\n", cps);
8155 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8156 // we have a real problem now, as SendBoard will use the a2a3 kludge
8157 // when black is to move, while there might be nothing on a2 or black
8158 // might already have the move. So send the board as if white has the move.
8159 // But first we must change the stm of the engine, as it refused the last move
8160 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8161 if(WhiteOnMove(forwardMostMove)) {
8162 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8163 SendBoard(cps, forwardMostMove); // kludgeless board
8165 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8166 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8167 SendBoard(cps, forwardMostMove+1); // kludgeless board
8169 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8170 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8171 gameMode == TwoMachinesPlay)
8172 SendToProgram("go\n", cps);
8175 if (gameMode == PlayFromGameFile) {
8176 /* Stop reading this game file */
8177 gameMode = EditGame;
8180 /* [HGM] illegal-move claim should forfeit game when Xboard */
8181 /* only passes fully legal moves */
8182 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8183 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8184 "False illegal-move claim", GE_XBOARD );
8185 return; // do not take back move we tested as valid
8187 currentMove = forwardMostMove-1;
8188 DisplayMove(currentMove-1); /* before DisplayMoveError */
8189 SwitchClocks(forwardMostMove-1); // [HGM] race
8190 DisplayBothClocks();
8191 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8192 parseList[currentMove], _(cps->which));
8193 DisplayMoveError(buf1);
8194 DrawPosition(FALSE, boards[currentMove]);
8197 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8198 /* Program has a broken "time" command that
8199 outputs a string not ending in newline.
8205 * If chess program startup fails, exit with an error message.
8206 * Attempts to recover here are futile.
8208 if ((StrStr(message, "unknown host") != NULL)
8209 || (StrStr(message, "No remote directory") != NULL)
8210 || (StrStr(message, "not found") != NULL)
8211 || (StrStr(message, "No such file") != NULL)
8212 || (StrStr(message, "can't alloc") != NULL)
8213 || (StrStr(message, "Permission denied") != NULL)) {
8215 cps->maybeThinking = FALSE;
8216 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8217 _(cps->which), cps->program, cps->host, message);
8218 RemoveInputSource(cps->isr);
8219 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8220 if(cps == &first) appData.noChessProgram = TRUE;
8221 DisplayError(buf1, 0);
8227 * Look for hint output
8229 if (sscanf(message, "Hint: %s", buf1) == 1) {
8230 if (cps == &first && hintRequested) {
8231 hintRequested = FALSE;
8232 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8233 &fromX, &fromY, &toX, &toY, &promoChar)) {
8234 (void) CoordsToAlgebraic(boards[forwardMostMove],
8235 PosFlags(forwardMostMove),
8236 fromY, fromX, toY, toX, promoChar, buf1);
8237 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8238 DisplayInformation(buf2);
8240 /* Hint move could not be parsed!? */
8241 snprintf(buf2, sizeof(buf2),
8242 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8243 buf1, _(cps->which));
8244 DisplayError(buf2, 0);
8247 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8253 * Ignore other messages if game is not in progress
8255 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8256 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8259 * look for win, lose, draw, or draw offer
8261 if (strncmp(message, "1-0", 3) == 0) {
8262 char *p, *q, *r = "";
8263 p = strchr(message, '{');
8271 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8273 } else if (strncmp(message, "0-1", 3) == 0) {
8274 char *p, *q, *r = "";
8275 p = strchr(message, '{');
8283 /* Kludge for Arasan 4.1 bug */
8284 if (strcmp(r, "Black resigns") == 0) {
8285 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8288 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8290 } else if (strncmp(message, "1/2", 3) == 0) {
8291 char *p, *q, *r = "";
8292 p = strchr(message, '{');
8301 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8304 } else if (strncmp(message, "White resign", 12) == 0) {
8305 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8307 } else if (strncmp(message, "Black resign", 12) == 0) {
8308 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8310 } else if (strncmp(message, "White matches", 13) == 0 ||
8311 strncmp(message, "Black matches", 13) == 0 ) {
8312 /* [HGM] ignore GNUShogi noises */
8314 } else if (strncmp(message, "White", 5) == 0 &&
8315 message[5] != '(' &&
8316 StrStr(message, "Black") == NULL) {
8317 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8319 } else if (strncmp(message, "Black", 5) == 0 &&
8320 message[5] != '(') {
8321 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8323 } else if (strcmp(message, "resign") == 0 ||
8324 strcmp(message, "computer resigns") == 0) {
8326 case MachinePlaysBlack:
8327 case IcsPlayingBlack:
8328 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8330 case MachinePlaysWhite:
8331 case IcsPlayingWhite:
8332 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8334 case TwoMachinesPlay:
8335 if (cps->twoMachinesColor[0] == 'w')
8336 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8338 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8345 } else if (strncmp(message, "opponent mates", 14) == 0) {
8347 case MachinePlaysBlack:
8348 case IcsPlayingBlack:
8349 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8351 case MachinePlaysWhite:
8352 case IcsPlayingWhite:
8353 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8355 case TwoMachinesPlay:
8356 if (cps->twoMachinesColor[0] == 'w')
8357 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8359 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8366 } else if (strncmp(message, "computer mates", 14) == 0) {
8368 case MachinePlaysBlack:
8369 case IcsPlayingBlack:
8370 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8372 case MachinePlaysWhite:
8373 case IcsPlayingWhite:
8374 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8376 case TwoMachinesPlay:
8377 if (cps->twoMachinesColor[0] == 'w')
8378 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8380 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8387 } else if (strncmp(message, "checkmate", 9) == 0) {
8388 if (WhiteOnMove(forwardMostMove)) {
8389 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8391 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8394 } else if (strstr(message, "Draw") != NULL ||
8395 strstr(message, "game is a draw") != NULL) {
8396 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8398 } else if (strstr(message, "offer") != NULL &&
8399 strstr(message, "draw") != NULL) {
8401 if (appData.zippyPlay && first.initDone) {
8402 /* Relay offer to ICS */
8403 SendToICS(ics_prefix);
8404 SendToICS("draw\n");
8407 cps->offeredDraw = 2; /* valid until this engine moves twice */
8408 if (gameMode == TwoMachinesPlay) {
8409 if (cps->other->offeredDraw) {
8410 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8411 /* [HGM] in two-machine mode we delay relaying draw offer */
8412 /* until after we also have move, to see if it is really claim */
8414 } else if (gameMode == MachinePlaysWhite ||
8415 gameMode == MachinePlaysBlack) {
8416 if (userOfferedDraw) {
8417 DisplayInformation(_("Machine accepts your draw offer"));
8418 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8420 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8427 * Look for thinking output
8429 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8430 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8432 int plylev, mvleft, mvtot, curscore, time;
8433 char mvname[MOVE_LEN];
8437 int prefixHint = FALSE;
8438 mvname[0] = NULLCHAR;
8441 case MachinePlaysBlack:
8442 case IcsPlayingBlack:
8443 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8445 case MachinePlaysWhite:
8446 case IcsPlayingWhite:
8447 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8452 case IcsObserving: /* [DM] icsEngineAnalyze */
8453 if (!appData.icsEngineAnalyze) ignore = TRUE;
8455 case TwoMachinesPlay:
8456 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8466 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8468 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8469 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8471 if (plyext != ' ' && plyext != '\t') {
8475 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8476 if( cps->scoreIsAbsolute &&
8477 ( gameMode == MachinePlaysBlack ||
8478 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8479 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8480 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8481 !WhiteOnMove(currentMove)
8484 curscore = -curscore;
8488 tempStats.depth = plylev;
8489 tempStats.nodes = nodes;
8490 tempStats.time = time;
8491 tempStats.score = curscore;
8492 tempStats.got_only_move = 0;
8494 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8497 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8498 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8499 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8500 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8501 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8502 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8503 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8504 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8507 /* Buffer overflow protection */
8508 if (buf1[0] != NULLCHAR) {
8509 if (strlen(buf1) >= sizeof(tempStats.movelist)
8510 && appData.debugMode) {
8512 "PV is too long; using the first %u bytes.\n",
8513 (unsigned) sizeof(tempStats.movelist) - 1);
8516 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8518 sprintf(tempStats.movelist, " no PV\n");
8521 if (tempStats.seen_stat) {
8522 tempStats.ok_to_send = 1;
8525 if (strchr(tempStats.movelist, '(') != NULL) {
8526 tempStats.line_is_book = 1;
8527 tempStats.nr_moves = 0;
8528 tempStats.moves_left = 0;
8530 tempStats.line_is_book = 0;
8533 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8534 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8536 SendProgramStatsToFrontend( cps, &tempStats );
8539 [AS] Protect the thinkOutput buffer from overflow... this
8540 is only useful if buf1 hasn't overflowed first!
8542 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8544 (gameMode == TwoMachinesPlay ?
8545 ToUpper(cps->twoMachinesColor[0]) : ' '),
8546 ((double) curscore) / 100.0,
8547 prefixHint ? lastHint : "",
8548 prefixHint ? " " : "" );
8550 if( buf1[0] != NULLCHAR ) {
8551 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8553 if( strlen(buf1) > max_len ) {
8554 if( appData.debugMode) {
8555 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8557 buf1[max_len+1] = '\0';
8560 strcat( thinkOutput, buf1 );
8563 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8564 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8565 DisplayMove(currentMove - 1);
8569 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8570 /* crafty (9.25+) says "(only move) <move>"
8571 * if there is only 1 legal move
8573 sscanf(p, "(only move) %s", buf1);
8574 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8575 sprintf(programStats.movelist, "%s (only move)", buf1);
8576 programStats.depth = 1;
8577 programStats.nr_moves = 1;
8578 programStats.moves_left = 1;
8579 programStats.nodes = 1;
8580 programStats.time = 1;
8581 programStats.got_only_move = 1;
8583 /* Not really, but we also use this member to
8584 mean "line isn't going to change" (Crafty
8585 isn't searching, so stats won't change) */
8586 programStats.line_is_book = 1;
8588 SendProgramStatsToFrontend( cps, &programStats );
8590 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8591 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8592 DisplayMove(currentMove - 1);
8595 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8596 &time, &nodes, &plylev, &mvleft,
8597 &mvtot, mvname) >= 5) {
8598 /* The stat01: line is from Crafty (9.29+) in response
8599 to the "." command */
8600 programStats.seen_stat = 1;
8601 cps->maybeThinking = TRUE;
8603 if (programStats.got_only_move || !appData.periodicUpdates)
8606 programStats.depth = plylev;
8607 programStats.time = time;
8608 programStats.nodes = nodes;
8609 programStats.moves_left = mvleft;
8610 programStats.nr_moves = mvtot;
8611 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8612 programStats.ok_to_send = 1;
8613 programStats.movelist[0] = '\0';
8615 SendProgramStatsToFrontend( cps, &programStats );
8619 } else if (strncmp(message,"++",2) == 0) {
8620 /* Crafty 9.29+ outputs this */
8621 programStats.got_fail = 2;
8624 } else if (strncmp(message,"--",2) == 0) {
8625 /* Crafty 9.29+ outputs this */
8626 programStats.got_fail = 1;
8629 } else if (thinkOutput[0] != NULLCHAR &&
8630 strncmp(message, " ", 4) == 0) {
8631 unsigned message_len;
8634 while (*p && *p == ' ') p++;
8636 message_len = strlen( p );
8638 /* [AS] Avoid buffer overflow */
8639 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8640 strcat(thinkOutput, " ");
8641 strcat(thinkOutput, p);
8644 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8645 strcat(programStats.movelist, " ");
8646 strcat(programStats.movelist, p);
8649 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8650 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8651 DisplayMove(currentMove - 1);
8659 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8660 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8662 ChessProgramStats cpstats;
8664 if (plyext != ' ' && plyext != '\t') {
8668 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8669 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8670 curscore = -curscore;
8673 cpstats.depth = plylev;
8674 cpstats.nodes = nodes;
8675 cpstats.time = time;
8676 cpstats.score = curscore;
8677 cpstats.got_only_move = 0;
8678 cpstats.movelist[0] = '\0';
8680 if (buf1[0] != NULLCHAR) {
8681 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8684 cpstats.ok_to_send = 0;
8685 cpstats.line_is_book = 0;
8686 cpstats.nr_moves = 0;
8687 cpstats.moves_left = 0;
8689 SendProgramStatsToFrontend( cps, &cpstats );
8696 /* Parse a game score from the character string "game", and
8697 record it as the history of the current game. The game
8698 score is NOT assumed to start from the standard position.
8699 The display is not updated in any way.
8702 ParseGameHistory(game)
8706 int fromX, fromY, toX, toY, boardIndex;
8711 if (appData.debugMode)
8712 fprintf(debugFP, "Parsing game history: %s\n", game);
8714 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8715 gameInfo.site = StrSave(appData.icsHost);
8716 gameInfo.date = PGNDate();
8717 gameInfo.round = StrSave("-");
8719 /* Parse out names of players */
8720 while (*game == ' ') game++;
8722 while (*game != ' ') *p++ = *game++;
8724 gameInfo.white = StrSave(buf);
8725 while (*game == ' ') game++;
8727 while (*game != ' ' && *game != '\n') *p++ = *game++;
8729 gameInfo.black = StrSave(buf);
8732 boardIndex = blackPlaysFirst ? 1 : 0;
8735 yyboardindex = boardIndex;
8736 moveType = (ChessMove) Myylex();
8738 case IllegalMove: /* maybe suicide chess, etc. */
8739 if (appData.debugMode) {
8740 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8741 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8742 setbuf(debugFP, NULL);
8744 case WhitePromotion:
8745 case BlackPromotion:
8746 case WhiteNonPromotion:
8747 case BlackNonPromotion:
8749 case WhiteCapturesEnPassant:
8750 case BlackCapturesEnPassant:
8751 case WhiteKingSideCastle:
8752 case WhiteQueenSideCastle:
8753 case BlackKingSideCastle:
8754 case BlackQueenSideCastle:
8755 case WhiteKingSideCastleWild:
8756 case WhiteQueenSideCastleWild:
8757 case BlackKingSideCastleWild:
8758 case BlackQueenSideCastleWild:
8760 case WhiteHSideCastleFR:
8761 case WhiteASideCastleFR:
8762 case BlackHSideCastleFR:
8763 case BlackASideCastleFR:
8765 fromX = currentMoveString[0] - AAA;
8766 fromY = currentMoveString[1] - ONE;
8767 toX = currentMoveString[2] - AAA;
8768 toY = currentMoveString[3] - ONE;
8769 promoChar = currentMoveString[4];
8773 fromX = moveType == WhiteDrop ?
8774 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8775 (int) CharToPiece(ToLower(currentMoveString[0]));
8777 toX = currentMoveString[2] - AAA;
8778 toY = currentMoveString[3] - ONE;
8779 promoChar = NULLCHAR;
8783 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8784 if (appData.debugMode) {
8785 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8786 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8787 setbuf(debugFP, NULL);
8789 DisplayError(buf, 0);
8791 case ImpossibleMove:
8793 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8794 if (appData.debugMode) {
8795 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8796 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8797 setbuf(debugFP, NULL);
8799 DisplayError(buf, 0);
8802 if (boardIndex < backwardMostMove) {
8803 /* Oops, gap. How did that happen? */
8804 DisplayError(_("Gap in move list"), 0);
8807 backwardMostMove = blackPlaysFirst ? 1 : 0;
8808 if (boardIndex > forwardMostMove) {
8809 forwardMostMove = boardIndex;
8813 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8814 strcat(parseList[boardIndex-1], " ");
8815 strcat(parseList[boardIndex-1], yy_text);
8827 case GameUnfinished:
8828 if (gameMode == IcsExamining) {
8829 if (boardIndex < backwardMostMove) {
8830 /* Oops, gap. How did that happen? */
8833 backwardMostMove = blackPlaysFirst ? 1 : 0;
8836 gameInfo.result = moveType;
8837 p = strchr(yy_text, '{');
8838 if (p == NULL) p = strchr(yy_text, '(');
8841 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8843 q = strchr(p, *p == '{' ? '}' : ')');
8844 if (q != NULL) *q = NULLCHAR;
8847 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8848 gameInfo.resultDetails = StrSave(p);
8851 if (boardIndex >= forwardMostMove &&
8852 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8853 backwardMostMove = blackPlaysFirst ? 1 : 0;
8856 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8857 fromY, fromX, toY, toX, promoChar,
8858 parseList[boardIndex]);
8859 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8860 /* currentMoveString is set as a side-effect of yylex */
8861 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8862 strcat(moveList[boardIndex], "\n");
8864 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8865 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8871 if(gameInfo.variant != VariantShogi)
8872 strcat(parseList[boardIndex - 1], "+");
8876 strcat(parseList[boardIndex - 1], "#");
8883 /* Apply a move to the given board */
8885 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8886 int fromX, fromY, toX, toY;
8890 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8891 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8893 /* [HGM] compute & store e.p. status and castling rights for new position */
8894 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8896 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8897 oldEP = (signed char)board[EP_STATUS];
8898 board[EP_STATUS] = EP_NONE;
8900 if( board[toY][toX] != EmptySquare )
8901 board[EP_STATUS] = EP_CAPTURE;
8903 if (fromY == DROP_RANK) {
8905 piece = board[toY][toX] = (ChessSquare) fromX;
8909 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8910 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8911 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8913 if( board[fromY][fromX] == WhitePawn ) {
8914 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8915 board[EP_STATUS] = EP_PAWN_MOVE;
8917 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8918 gameInfo.variant != VariantBerolina || toX < fromX)
8919 board[EP_STATUS] = toX | berolina;
8920 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8921 gameInfo.variant != VariantBerolina || toX > fromX)
8922 board[EP_STATUS] = toX;
8925 if( board[fromY][fromX] == BlackPawn ) {
8926 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8927 board[EP_STATUS] = EP_PAWN_MOVE;
8928 if( toY-fromY== -2) {
8929 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8930 gameInfo.variant != VariantBerolina || toX < fromX)
8931 board[EP_STATUS] = toX | berolina;
8932 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8933 gameInfo.variant != VariantBerolina || toX > fromX)
8934 board[EP_STATUS] = toX;
8938 for(i=0; i<nrCastlingRights; i++) {
8939 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8940 board[CASTLING][i] == toX && castlingRank[i] == toY
8941 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8944 if (fromX == toX && fromY == toY) return;
8946 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8947 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8948 if(gameInfo.variant == VariantKnightmate)
8949 king += (int) WhiteUnicorn - (int) WhiteKing;
8951 /* Code added by Tord: */
8952 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8953 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8954 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8955 board[fromY][fromX] = EmptySquare;
8956 board[toY][toX] = EmptySquare;
8957 if((toX > fromX) != (piece == WhiteRook)) {
8958 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8960 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8962 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8963 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8964 board[fromY][fromX] = EmptySquare;
8965 board[toY][toX] = EmptySquare;
8966 if((toX > fromX) != (piece == BlackRook)) {
8967 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8969 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8971 /* End of code added by Tord */
8973 } else if (board[fromY][fromX] == king
8974 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8975 && toY == fromY && toX > fromX+1) {
8976 board[fromY][fromX] = EmptySquare;
8977 board[toY][toX] = king;
8978 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8979 board[fromY][BOARD_RGHT-1] = EmptySquare;
8980 } else if (board[fromY][fromX] == king
8981 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8982 && toY == fromY && toX < fromX-1) {
8983 board[fromY][fromX] = EmptySquare;
8984 board[toY][toX] = king;
8985 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8986 board[fromY][BOARD_LEFT] = EmptySquare;
8987 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8988 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8989 && toY >= BOARD_HEIGHT-promoRank
8991 /* white pawn promotion */
8992 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8993 if (board[toY][toX] == EmptySquare) {
8994 board[toY][toX] = WhiteQueen;
8996 if(gameInfo.variant==VariantBughouse ||
8997 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8998 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8999 board[fromY][fromX] = EmptySquare;
9000 } else if ((fromY == BOARD_HEIGHT-4)
9002 && gameInfo.variant != VariantXiangqi
9003 && gameInfo.variant != VariantBerolina
9004 && (board[fromY][fromX] == WhitePawn)
9005 && (board[toY][toX] == EmptySquare)) {
9006 board[fromY][fromX] = EmptySquare;
9007 board[toY][toX] = WhitePawn;
9008 captured = board[toY - 1][toX];
9009 board[toY - 1][toX] = EmptySquare;
9010 } else if ((fromY == BOARD_HEIGHT-4)
9012 && gameInfo.variant == VariantBerolina
9013 && (board[fromY][fromX] == WhitePawn)
9014 && (board[toY][toX] == EmptySquare)) {
9015 board[fromY][fromX] = EmptySquare;
9016 board[toY][toX] = WhitePawn;
9017 if(oldEP & EP_BEROLIN_A) {
9018 captured = board[fromY][fromX-1];
9019 board[fromY][fromX-1] = EmptySquare;
9020 }else{ captured = board[fromY][fromX+1];
9021 board[fromY][fromX+1] = EmptySquare;
9023 } else if (board[fromY][fromX] == king
9024 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9025 && toY == fromY && toX > fromX+1) {
9026 board[fromY][fromX] = EmptySquare;
9027 board[toY][toX] = king;
9028 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9029 board[fromY][BOARD_RGHT-1] = EmptySquare;
9030 } else if (board[fromY][fromX] == king
9031 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9032 && toY == fromY && toX < fromX-1) {
9033 board[fromY][fromX] = EmptySquare;
9034 board[toY][toX] = king;
9035 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9036 board[fromY][BOARD_LEFT] = EmptySquare;
9037 } else if (fromY == 7 && fromX == 3
9038 && board[fromY][fromX] == BlackKing
9039 && toY == 7 && toX == 5) {
9040 board[fromY][fromX] = EmptySquare;
9041 board[toY][toX] = BlackKing;
9042 board[fromY][7] = EmptySquare;
9043 board[toY][4] = BlackRook;
9044 } else if (fromY == 7 && fromX == 3
9045 && board[fromY][fromX] == BlackKing
9046 && toY == 7 && toX == 1) {
9047 board[fromY][fromX] = EmptySquare;
9048 board[toY][toX] = BlackKing;
9049 board[fromY][0] = EmptySquare;
9050 board[toY][2] = BlackRook;
9051 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9052 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9055 /* black pawn promotion */
9056 board[toY][toX] = CharToPiece(ToLower(promoChar));
9057 if (board[toY][toX] == EmptySquare) {
9058 board[toY][toX] = BlackQueen;
9060 if(gameInfo.variant==VariantBughouse ||
9061 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9062 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9063 board[fromY][fromX] = EmptySquare;
9064 } else if ((fromY == 3)
9066 && gameInfo.variant != VariantXiangqi
9067 && gameInfo.variant != VariantBerolina
9068 && (board[fromY][fromX] == BlackPawn)
9069 && (board[toY][toX] == EmptySquare)) {
9070 board[fromY][fromX] = EmptySquare;
9071 board[toY][toX] = BlackPawn;
9072 captured = board[toY + 1][toX];
9073 board[toY + 1][toX] = EmptySquare;
9074 } else if ((fromY == 3)
9076 && gameInfo.variant == VariantBerolina
9077 && (board[fromY][fromX] == BlackPawn)
9078 && (board[toY][toX] == EmptySquare)) {
9079 board[fromY][fromX] = EmptySquare;
9080 board[toY][toX] = BlackPawn;
9081 if(oldEP & EP_BEROLIN_A) {
9082 captured = board[fromY][fromX-1];
9083 board[fromY][fromX-1] = EmptySquare;
9084 }else{ captured = board[fromY][fromX+1];
9085 board[fromY][fromX+1] = EmptySquare;
9088 board[toY][toX] = board[fromY][fromX];
9089 board[fromY][fromX] = EmptySquare;
9093 if (gameInfo.holdingsWidth != 0) {
9095 /* !!A lot more code needs to be written to support holdings */
9096 /* [HGM] OK, so I have written it. Holdings are stored in the */
9097 /* penultimate board files, so they are automaticlly stored */
9098 /* in the game history. */
9099 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9100 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9101 /* Delete from holdings, by decreasing count */
9102 /* and erasing image if necessary */
9103 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9104 if(p < (int) BlackPawn) { /* white drop */
9105 p -= (int)WhitePawn;
9106 p = PieceToNumber((ChessSquare)p);
9107 if(p >= gameInfo.holdingsSize) p = 0;
9108 if(--board[p][BOARD_WIDTH-2] <= 0)
9109 board[p][BOARD_WIDTH-1] = EmptySquare;
9110 if((int)board[p][BOARD_WIDTH-2] < 0)
9111 board[p][BOARD_WIDTH-2] = 0;
9112 } else { /* black drop */
9113 p -= (int)BlackPawn;
9114 p = PieceToNumber((ChessSquare)p);
9115 if(p >= gameInfo.holdingsSize) p = 0;
9116 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9117 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9118 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9119 board[BOARD_HEIGHT-1-p][1] = 0;
9122 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9123 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9124 /* [HGM] holdings: Add to holdings, if holdings exist */
9125 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9126 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9127 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9130 if (p >= (int) BlackPawn) {
9131 p -= (int)BlackPawn;
9132 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9133 /* in Shogi restore piece to its original first */
9134 captured = (ChessSquare) (DEMOTED captured);
9137 p = PieceToNumber((ChessSquare)p);
9138 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9139 board[p][BOARD_WIDTH-2]++;
9140 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9142 p -= (int)WhitePawn;
9143 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9144 captured = (ChessSquare) (DEMOTED captured);
9147 p = PieceToNumber((ChessSquare)p);
9148 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9149 board[BOARD_HEIGHT-1-p][1]++;
9150 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9153 } else if (gameInfo.variant == VariantAtomic) {
9154 if (captured != EmptySquare) {
9156 for (y = toY-1; y <= toY+1; y++) {
9157 for (x = toX-1; x <= toX+1; x++) {
9158 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9159 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9160 board[y][x] = EmptySquare;
9164 board[toY][toX] = EmptySquare;
9167 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9168 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9170 if(promoChar == '+') {
9171 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9172 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9173 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9174 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9176 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9177 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9178 // [HGM] superchess: take promotion piece out of holdings
9179 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9180 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9181 if(!--board[k][BOARD_WIDTH-2])
9182 board[k][BOARD_WIDTH-1] = EmptySquare;
9184 if(!--board[BOARD_HEIGHT-1-k][1])
9185 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9191 /* Updates forwardMostMove */
9193 MakeMove(fromX, fromY, toX, toY, promoChar)
9194 int fromX, fromY, toX, toY;
9197 // forwardMostMove++; // [HGM] bare: moved downstream
9199 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9200 int timeLeft; static int lastLoadFlag=0; int king, piece;
9201 piece = boards[forwardMostMove][fromY][fromX];
9202 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9203 if(gameInfo.variant == VariantKnightmate)
9204 king += (int) WhiteUnicorn - (int) WhiteKing;
9205 if(forwardMostMove == 0) {
9207 fprintf(serverMoves, "%s;", second.tidy);
9208 fprintf(serverMoves, "%s;", first.tidy);
9209 if(!blackPlaysFirst)
9210 fprintf(serverMoves, "%s;", second.tidy);
9211 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9212 lastLoadFlag = loadFlag;
9214 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9215 // print castling suffix
9216 if( toY == fromY && piece == king ) {
9218 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9220 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9223 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9224 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9225 boards[forwardMostMove][toY][toX] == EmptySquare
9226 && fromX != toX && fromY != toY)
9227 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9229 if(promoChar != NULLCHAR)
9230 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9232 fprintf(serverMoves, "/%d/%d",
9233 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9234 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9235 else timeLeft = blackTimeRemaining/1000;
9236 fprintf(serverMoves, "/%d", timeLeft);
9238 fflush(serverMoves);
9241 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9242 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9246 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9247 if (commentList[forwardMostMove+1] != NULL) {
9248 free(commentList[forwardMostMove+1]);
9249 commentList[forwardMostMove+1] = NULL;
9251 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9252 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9253 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9254 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9255 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9256 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9257 gameInfo.result = GameUnfinished;
9258 if (gameInfo.resultDetails != NULL) {
9259 free(gameInfo.resultDetails);
9260 gameInfo.resultDetails = NULL;
9262 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9263 moveList[forwardMostMove - 1]);
9264 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9265 PosFlags(forwardMostMove - 1),
9266 fromY, fromX, toY, toX, promoChar,
9267 parseList[forwardMostMove - 1]);
9268 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9274 if(gameInfo.variant != VariantShogi)
9275 strcat(parseList[forwardMostMove - 1], "+");
9279 strcat(parseList[forwardMostMove - 1], "#");
9282 if (appData.debugMode) {
9283 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9288 /* Updates currentMove if not pausing */
9290 ShowMove(fromX, fromY, toX, toY)
9292 int instant = (gameMode == PlayFromGameFile) ?
9293 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9294 if(appData.noGUI) return;
9295 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9297 if (forwardMostMove == currentMove + 1) {
9298 AnimateMove(boards[forwardMostMove - 1],
9299 fromX, fromY, toX, toY);
9301 if (appData.highlightLastMove) {
9302 SetHighlights(fromX, fromY, toX, toY);
9305 currentMove = forwardMostMove;
9308 if (instant) return;
9310 DisplayMove(currentMove - 1);
9311 DrawPosition(FALSE, boards[currentMove]);
9312 DisplayBothClocks();
9313 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9314 DisplayBook(currentMove);
9317 void SendEgtPath(ChessProgramState *cps)
9318 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9319 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9321 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9324 char c, *q = name+1, *r, *s;
9326 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9327 while(*p && *p != ',') *q++ = *p++;
9329 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9330 strcmp(name, ",nalimov:") == 0 ) {
9331 // take nalimov path from the menu-changeable option first, if it is defined
9332 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9333 SendToProgram(buf,cps); // send egtbpath command for nalimov
9335 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9336 (s = StrStr(appData.egtFormats, name)) != NULL) {
9337 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9338 s = r = StrStr(s, ":") + 1; // beginning of path info
9339 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9340 c = *r; *r = 0; // temporarily null-terminate path info
9341 *--q = 0; // strip of trailig ':' from name
9342 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9344 SendToProgram(buf,cps); // send egtbpath command for this format
9346 if(*p == ',') p++; // read away comma to position for next format name
9351 InitChessProgram(cps, setup)
9352 ChessProgramState *cps;
9353 int setup; /* [HGM] needed to setup FRC opening position */
9355 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9356 if (appData.noChessProgram) return;
9357 hintRequested = FALSE;
9358 bookRequested = FALSE;
9360 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9361 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9362 if(cps->memSize) { /* [HGM] memory */
9363 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9364 SendToProgram(buf, cps);
9366 SendEgtPath(cps); /* [HGM] EGT */
9367 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9368 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9369 SendToProgram(buf, cps);
9372 SendToProgram(cps->initString, cps);
9373 if (gameInfo.variant != VariantNormal &&
9374 gameInfo.variant != VariantLoadable
9375 /* [HGM] also send variant if board size non-standard */
9376 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9378 char *v = VariantName(gameInfo.variant);
9379 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9380 /* [HGM] in protocol 1 we have to assume all variants valid */
9381 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9382 DisplayFatalError(buf, 0, 1);
9386 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9387 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9388 if( gameInfo.variant == VariantXiangqi )
9389 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9390 if( gameInfo.variant == VariantShogi )
9391 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9392 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9393 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9394 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9395 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9396 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9397 if( gameInfo.variant == VariantCourier )
9398 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9399 if( gameInfo.variant == VariantSuper )
9400 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9401 if( gameInfo.variant == VariantGreat )
9402 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9403 if( gameInfo.variant == VariantSChess )
9404 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9407 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9408 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9409 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9410 if(StrStr(cps->variants, b) == NULL) {
9411 // specific sized variant not known, check if general sizing allowed
9412 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9413 if(StrStr(cps->variants, "boardsize") == NULL) {
9414 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9415 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9416 DisplayFatalError(buf, 0, 1);
9419 /* [HGM] here we really should compare with the maximum supported board size */
9422 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9423 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9424 SendToProgram(buf, cps);
9426 currentlyInitializedVariant = gameInfo.variant;
9428 /* [HGM] send opening position in FRC to first engine */
9430 SendToProgram("force\n", cps);
9432 /* engine is now in force mode! Set flag to wake it up after first move. */
9433 setboardSpoiledMachineBlack = 1;
9437 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9438 SendToProgram(buf, cps);
9440 cps->maybeThinking = FALSE;
9441 cps->offeredDraw = 0;
9442 if (!appData.icsActive) {
9443 SendTimeControl(cps, movesPerSession, timeControl,
9444 timeIncrement, appData.searchDepth,
9447 if (appData.showThinking
9448 // [HGM] thinking: four options require thinking output to be sent
9449 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9451 SendToProgram("post\n", cps);
9453 SendToProgram("hard\n", cps);
9454 if (!appData.ponderNextMove) {
9455 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9456 it without being sure what state we are in first. "hard"
9457 is not a toggle, so that one is OK.
9459 SendToProgram("easy\n", cps);
9462 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9463 SendToProgram(buf, cps);
9465 cps->initDone = TRUE;
9470 StartChessProgram(cps)
9471 ChessProgramState *cps;
9476 if (appData.noChessProgram) return;
9477 cps->initDone = FALSE;
9479 if (strcmp(cps->host, "localhost") == 0) {
9480 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9481 } else if (*appData.remoteShell == NULLCHAR) {
9482 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9484 if (*appData.remoteUser == NULLCHAR) {
9485 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9488 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9489 cps->host, appData.remoteUser, cps->program);
9491 err = StartChildProcess(buf, "", &cps->pr);
9495 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9496 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9497 if(cps != &first) return;
9498 appData.noChessProgram = TRUE;
9501 // DisplayFatalError(buf, err, 1);
9502 // cps->pr = NoProc;
9507 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9508 if (cps->protocolVersion > 1) {
9509 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9510 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9511 cps->comboCnt = 0; // and values of combo boxes
9512 SendToProgram(buf, cps);
9514 SendToProgram("xboard\n", cps);
9519 TwoMachinesEventIfReady P((void))
9521 static int curMess = 0;
9522 if (first.lastPing != first.lastPong) {
9523 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9524 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9527 if (second.lastPing != second.lastPong) {
9528 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9529 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9532 DisplayMessage("", ""); curMess = 0;
9538 CountPlayers(char *p)
9541 while(p = strchr(p, '\n')) p++, n++; // count participants
9546 WriteTourneyFile(char *results)
9547 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9548 FILE *f = fopen(appData.tourneyFile, "w");
9549 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9550 // create a file with tournament description
9551 fprintf(f, "-participants {%s}\n", appData.participants);
9552 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9553 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9554 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9555 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9556 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9557 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9558 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9559 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9560 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9561 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9562 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9564 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9566 fprintf(f, "-mps %d\n", appData.movesPerSession);
9567 fprintf(f, "-tc %s\n", appData.timeControl);
9568 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9570 fprintf(f, "-results \"%s\"\n", results);
9576 CreateTourney(char *name)
9579 if(name[0] == NULLCHAR) {
9580 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9583 f = fopen(appData.tourneyFile, "r");
9584 if(f) { // file exists
9585 ParseArgsFromFile(f); // parse it
9587 if(CountPlayers(appData.participants) < appData.tourneyType + (!appData.tourneyType) + 1) {
9588 DisplayError(_("Not enough participants"), 0);
9591 if((f = WriteTourneyFile("")) == NULL) return 0;
9594 appData.noChessProgram = FALSE;
9595 appData.clockMode = TRUE;
9600 #define MAXENGINES 1000
9601 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9603 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9605 char buf[MSG_SIZ], *p, *q;
9609 while(*p && *p != '\n') *q++ = *p++;
9611 if(engineList[i]) free(engineList[i]);
9612 engineList[i] = strdup(buf);
9614 TidyProgramName(engineList[i], "localhost", buf);
9615 if(engineMnemonic[i]) free(engineMnemonic[i]);
9616 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9618 sscanf(q + 8, "%s", buf + strlen(buf));
9621 engineMnemonic[i] = strdup(buf);
9623 if(i > MAXENGINES - 2) break;
9625 engineList[i] = NULL;
9628 // following implemented as macro to avoid type limitations
9629 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9631 void SwapEngines(int n)
9632 { // swap settings for first engine and other engine (so far only some selected options)
9637 SWAP(chessProgram, p)
9639 SWAP(hasOwnBookUCI, h)
9640 SWAP(protocolVersion, h)
9642 SWAP(scoreIsAbsolute, h)
9649 SetPlayer(int player)
9650 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9652 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9653 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9654 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9655 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9657 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9658 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9659 ParseArgsFromString(buf);
9665 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9666 { // determine players from game number
9667 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9669 if(appData.tourneyType == 0) {
9670 roundsPerCycle = (nPlayers - 1) | 1;
9671 pairingsPerRound = nPlayers / 2;
9672 } else if(appData.tourneyType > 0) {
9673 roundsPerCycle = nPlayers - appData.tourneyType;
9674 pairingsPerRound = appData.tourneyType;
9676 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9677 gamesPerCycle = gamesPerRound * roundsPerCycle;
9678 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9679 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9680 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9681 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9682 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9683 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9685 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9686 if(appData.roundSync) *syncInterval = gamesPerRound;
9688 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9690 if(appData.tourneyType == 0) {
9691 if(curPairing == (nPlayers-1)/2 ) {
9692 *whitePlayer = curRound;
9693 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9695 *whitePlayer = curRound - pairingsPerRound + curPairing;
9696 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9697 *blackPlayer = curRound + pairingsPerRound - curPairing;
9698 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9700 } else if(appData.tourneyType > 0) {
9701 *whitePlayer = curPairing;
9702 *blackPlayer = curRound + appData.tourneyType;
9705 // take care of white/black alternation per round.
9706 // For cycles and games this is already taken care of by default, derived from matchGame!
9707 return curRound & 1;
9711 NextTourneyGame(int nr, int *swapColors)
9712 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9714 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9716 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9717 tf = fopen(appData.tourneyFile, "r");
9718 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9719 ParseArgsFromFile(tf); fclose(tf);
9720 InitTimeControls(); // TC might be altered from tourney file
9722 p = appData.participants;
9723 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9724 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9727 p = q = appData.results;
9728 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9729 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9730 DisplayMessage(_("Waiting for other game(s)"),"");
9731 waitingForGame = TRUE;
9732 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9735 waitingForGame = FALSE;
9738 if(first.pr != NoProc) return 1; // engines already loaded
9740 // redefine engines, engine dir, etc.
9741 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9742 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9744 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9745 SwapEngines(1); // and make that valid for second engine by swapping
9746 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9747 InitEngine(&second, 1);
9748 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9754 { // performs game initialization that does not invoke engines, and then tries to start the game
9755 int firstWhite, swapColors = 0;
9756 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9757 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9758 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9759 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9760 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9761 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9762 Reset(FALSE, first.pr != NoProc);
9763 appData.noChessProgram = FALSE;
9764 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9768 void UserAdjudicationEvent( int result )
9770 ChessMove gameResult = GameIsDrawn;
9773 gameResult = WhiteWins;
9775 else if( result < 0 ) {
9776 gameResult = BlackWins;
9779 if( gameMode == TwoMachinesPlay ) {
9780 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9785 // [HGM] save: calculate checksum of game to make games easily identifiable
9786 int StringCheckSum(char *s)
9789 if(s==NULL) return 0;
9790 while(*s) i = i*259 + *s++;
9797 for(i=backwardMostMove; i<forwardMostMove; i++) {
9798 sum += pvInfoList[i].depth;
9799 sum += StringCheckSum(parseList[i]);
9800 sum += StringCheckSum(commentList[i]);
9803 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9804 return sum + StringCheckSum(commentList[i]);
9805 } // end of save patch
9808 GameEnds(result, resultDetails, whosays)
9810 char *resultDetails;
9813 GameMode nextGameMode;
9815 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9817 if(endingGame) return; /* [HGM] crash: forbid recursion */
9819 if(twoBoards) { // [HGM] dual: switch back to one board
9820 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9821 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9823 if (appData.debugMode) {
9824 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9825 result, resultDetails ? resultDetails : "(null)", whosays);
9828 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9830 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9831 /* If we are playing on ICS, the server decides when the
9832 game is over, but the engine can offer to draw, claim
9836 if (appData.zippyPlay && first.initDone) {
9837 if (result == GameIsDrawn) {
9838 /* In case draw still needs to be claimed */
9839 SendToICS(ics_prefix);
9840 SendToICS("draw\n");
9841 } else if (StrCaseStr(resultDetails, "resign")) {
9842 SendToICS(ics_prefix);
9843 SendToICS("resign\n");
9847 endingGame = 0; /* [HGM] crash */
9851 /* If we're loading the game from a file, stop */
9852 if (whosays == GE_FILE) {
9853 (void) StopLoadGameTimer();
9857 /* Cancel draw offers */
9858 first.offeredDraw = second.offeredDraw = 0;
9860 /* If this is an ICS game, only ICS can really say it's done;
9861 if not, anyone can. */
9862 isIcsGame = (gameMode == IcsPlayingWhite ||
9863 gameMode == IcsPlayingBlack ||
9864 gameMode == IcsObserving ||
9865 gameMode == IcsExamining);
9867 if (!isIcsGame || whosays == GE_ICS) {
9868 /* OK -- not an ICS game, or ICS said it was done */
9870 if (!isIcsGame && !appData.noChessProgram)
9871 SetUserThinkingEnables();
9873 /* [HGM] if a machine claims the game end we verify this claim */
9874 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9875 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9877 ChessMove trueResult = (ChessMove) -1;
9879 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9880 first.twoMachinesColor[0] :
9881 second.twoMachinesColor[0] ;
9883 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9884 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9885 /* [HGM] verify: engine mate claims accepted if they were flagged */
9886 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9888 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9889 /* [HGM] verify: engine mate claims accepted if they were flagged */
9890 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9892 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9893 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9896 // now verify win claims, but not in drop games, as we don't understand those yet
9897 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9898 || gameInfo.variant == VariantGreat) &&
9899 (result == WhiteWins && claimer == 'w' ||
9900 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9901 if (appData.debugMode) {
9902 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9903 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9905 if(result != trueResult) {
9906 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9907 result = claimer == 'w' ? BlackWins : WhiteWins;
9908 resultDetails = buf;
9911 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9912 && (forwardMostMove <= backwardMostMove ||
9913 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9914 (claimer=='b')==(forwardMostMove&1))
9916 /* [HGM] verify: draws that were not flagged are false claims */
9917 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9918 result = claimer == 'w' ? BlackWins : WhiteWins;
9919 resultDetails = buf;
9921 /* (Claiming a loss is accepted no questions asked!) */
9923 /* [HGM] bare: don't allow bare King to win */
9924 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9925 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9926 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9927 && result != GameIsDrawn)
9928 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9929 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9930 int p = (signed char)boards[forwardMostMove][i][j] - color;
9931 if(p >= 0 && p <= (int)WhiteKing) k++;
9933 if (appData.debugMode) {
9934 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9935 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9938 result = GameIsDrawn;
9939 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9940 resultDetails = buf;
9946 if(serverMoves != NULL && !loadFlag) { char c = '=';
9947 if(result==WhiteWins) c = '+';
9948 if(result==BlackWins) c = '-';
9949 if(resultDetails != NULL)
9950 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9952 if (resultDetails != NULL) {
9953 gameInfo.result = result;
9954 gameInfo.resultDetails = StrSave(resultDetails);
9956 /* display last move only if game was not loaded from file */
9957 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9958 DisplayMove(currentMove - 1);
9960 if (forwardMostMove != 0) {
9961 if (gameMode != PlayFromGameFile && gameMode != EditGame
9962 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9964 if (*appData.saveGameFile != NULLCHAR) {
9965 SaveGameToFile(appData.saveGameFile, TRUE);
9966 } else if (appData.autoSaveGames) {
9969 if (*appData.savePositionFile != NULLCHAR) {
9970 SavePositionToFile(appData.savePositionFile);
9975 /* Tell program how game ended in case it is learning */
9976 /* [HGM] Moved this to after saving the PGN, just in case */
9977 /* engine died and we got here through time loss. In that */
9978 /* case we will get a fatal error writing the pipe, which */
9979 /* would otherwise lose us the PGN. */
9980 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9981 /* output during GameEnds should never be fatal anymore */
9982 if (gameMode == MachinePlaysWhite ||
9983 gameMode == MachinePlaysBlack ||
9984 gameMode == TwoMachinesPlay ||
9985 gameMode == IcsPlayingWhite ||
9986 gameMode == IcsPlayingBlack ||
9987 gameMode == BeginningOfGame) {
9989 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9991 if (first.pr != NoProc) {
9992 SendToProgram(buf, &first);
9994 if (second.pr != NoProc &&
9995 gameMode == TwoMachinesPlay) {
9996 SendToProgram(buf, &second);
10001 if (appData.icsActive) {
10002 if (appData.quietPlay &&
10003 (gameMode == IcsPlayingWhite ||
10004 gameMode == IcsPlayingBlack)) {
10005 SendToICS(ics_prefix);
10006 SendToICS("set shout 1\n");
10008 nextGameMode = IcsIdle;
10009 ics_user_moved = FALSE;
10010 /* clean up premove. It's ugly when the game has ended and the
10011 * premove highlights are still on the board.
10014 gotPremove = FALSE;
10015 ClearPremoveHighlights();
10016 DrawPosition(FALSE, boards[currentMove]);
10018 if (whosays == GE_ICS) {
10021 if (gameMode == IcsPlayingWhite)
10023 else if(gameMode == IcsPlayingBlack)
10024 PlayIcsLossSound();
10027 if (gameMode == IcsPlayingBlack)
10029 else if(gameMode == IcsPlayingWhite)
10030 PlayIcsLossSound();
10033 PlayIcsDrawSound();
10036 PlayIcsUnfinishedSound();
10039 } else if (gameMode == EditGame ||
10040 gameMode == PlayFromGameFile ||
10041 gameMode == AnalyzeMode ||
10042 gameMode == AnalyzeFile) {
10043 nextGameMode = gameMode;
10045 nextGameMode = EndOfGame;
10050 nextGameMode = gameMode;
10053 if (appData.noChessProgram) {
10054 gameMode = nextGameMode;
10056 endingGame = 0; /* [HGM] crash */
10061 /* Put first chess program into idle state */
10062 if (first.pr != NoProc &&
10063 (gameMode == MachinePlaysWhite ||
10064 gameMode == MachinePlaysBlack ||
10065 gameMode == TwoMachinesPlay ||
10066 gameMode == IcsPlayingWhite ||
10067 gameMode == IcsPlayingBlack ||
10068 gameMode == BeginningOfGame)) {
10069 SendToProgram("force\n", &first);
10070 if (first.usePing) {
10072 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10073 SendToProgram(buf, &first);
10076 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10077 /* Kill off first chess program */
10078 if (first.isr != NULL)
10079 RemoveInputSource(first.isr);
10082 if (first.pr != NoProc) {
10084 DoSleep( appData.delayBeforeQuit );
10085 SendToProgram("quit\n", &first);
10086 DoSleep( appData.delayAfterQuit );
10087 DestroyChildProcess(first.pr, first.useSigterm);
10091 if (second.reuse) {
10092 /* Put second chess program into idle state */
10093 if (second.pr != NoProc &&
10094 gameMode == TwoMachinesPlay) {
10095 SendToProgram("force\n", &second);
10096 if (second.usePing) {
10098 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10099 SendToProgram(buf, &second);
10102 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10103 /* Kill off second chess program */
10104 if (second.isr != NULL)
10105 RemoveInputSource(second.isr);
10108 if (second.pr != NoProc) {
10109 DoSleep( appData.delayBeforeQuit );
10110 SendToProgram("quit\n", &second);
10111 DoSleep( appData.delayAfterQuit );
10112 DestroyChildProcess(second.pr, second.useSigterm);
10114 second.pr = NoProc;
10117 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10118 char resChar = '=';
10122 if (first.twoMachinesColor[0] == 'w') {
10125 second.matchWins++;
10130 if (first.twoMachinesColor[0] == 'b') {
10133 second.matchWins++;
10136 case GameUnfinished:
10142 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10143 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10144 ReserveGame(nextGame, resChar); // sets nextGame
10145 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10146 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10148 if (nextGame <= appData.matchGames && !abortMatch) {
10149 gameMode = nextGameMode;
10150 matchGame = nextGame; // this will be overruled in tourney mode!
10151 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10152 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10153 endingGame = 0; /* [HGM] crash */
10156 gameMode = nextGameMode;
10157 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10158 first.tidy, second.tidy,
10159 first.matchWins, second.matchWins,
10160 appData.matchGames - (first.matchWins + second.matchWins));
10161 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10162 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10163 first.twoMachinesColor = "black\n";
10164 second.twoMachinesColor = "white\n";
10166 first.twoMachinesColor = "white\n";
10167 second.twoMachinesColor = "black\n";
10171 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10172 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10174 gameMode = nextGameMode;
10176 endingGame = 0; /* [HGM] crash */
10177 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10178 if(matchMode == TRUE) { // match through command line: exit with or without popup
10180 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10182 } else DisplayFatalError(buf, 0, 0);
10183 } else { // match through menu; just stop, with or without popup
10184 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10186 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10187 } else DisplayNote(buf);
10189 if(ranking) free(ranking);
10193 /* Assumes program was just initialized (initString sent).
10194 Leaves program in force mode. */
10196 FeedMovesToProgram(cps, upto)
10197 ChessProgramState *cps;
10202 if (appData.debugMode)
10203 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10204 startedFromSetupPosition ? "position and " : "",
10205 backwardMostMove, upto, cps->which);
10206 if(currentlyInitializedVariant != gameInfo.variant) {
10208 // [HGM] variantswitch: make engine aware of new variant
10209 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10210 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10211 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10212 SendToProgram(buf, cps);
10213 currentlyInitializedVariant = gameInfo.variant;
10215 SendToProgram("force\n", cps);
10216 if (startedFromSetupPosition) {
10217 SendBoard(cps, backwardMostMove);
10218 if (appData.debugMode) {
10219 fprintf(debugFP, "feedMoves\n");
10222 for (i = backwardMostMove; i < upto; i++) {
10223 SendMoveToProgram(i, cps);
10229 ResurrectChessProgram()
10231 /* The chess program may have exited.
10232 If so, restart it and feed it all the moves made so far. */
10233 static int doInit = 0;
10235 if (appData.noChessProgram) return 1;
10237 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10238 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10239 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10240 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10242 if (first.pr != NoProc) return 1;
10243 StartChessProgram(&first);
10245 InitChessProgram(&first, FALSE);
10246 FeedMovesToProgram(&first, currentMove);
10248 if (!first.sendTime) {
10249 /* can't tell gnuchess what its clock should read,
10250 so we bow to its notion. */
10252 timeRemaining[0][currentMove] = whiteTimeRemaining;
10253 timeRemaining[1][currentMove] = blackTimeRemaining;
10256 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10257 appData.icsEngineAnalyze) && first.analysisSupport) {
10258 SendToProgram("analyze\n", &first);
10259 first.analyzing = TRUE;
10265 * Button procedures
10268 Reset(redraw, init)
10273 if (appData.debugMode) {
10274 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10275 redraw, init, gameMode);
10277 CleanupTail(); // [HGM] vari: delete any stored variations
10278 pausing = pauseExamInvalid = FALSE;
10279 startedFromSetupPosition = blackPlaysFirst = FALSE;
10281 whiteFlag = blackFlag = FALSE;
10282 userOfferedDraw = FALSE;
10283 hintRequested = bookRequested = FALSE;
10284 first.maybeThinking = FALSE;
10285 second.maybeThinking = FALSE;
10286 first.bookSuspend = FALSE; // [HGM] book
10287 second.bookSuspend = FALSE;
10288 thinkOutput[0] = NULLCHAR;
10289 lastHint[0] = NULLCHAR;
10290 ClearGameInfo(&gameInfo);
10291 gameInfo.variant = StringToVariant(appData.variant);
10292 ics_user_moved = ics_clock_paused = FALSE;
10293 ics_getting_history = H_FALSE;
10295 white_holding[0] = black_holding[0] = NULLCHAR;
10296 ClearProgramStats();
10297 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10301 flipView = appData.flipView;
10302 ClearPremoveHighlights();
10303 gotPremove = FALSE;
10304 alarmSounded = FALSE;
10306 GameEnds(EndOfFile, NULL, GE_PLAYER);
10307 if(appData.serverMovesName != NULL) {
10308 /* [HGM] prepare to make moves file for broadcasting */
10309 clock_t t = clock();
10310 if(serverMoves != NULL) fclose(serverMoves);
10311 serverMoves = fopen(appData.serverMovesName, "r");
10312 if(serverMoves != NULL) {
10313 fclose(serverMoves);
10314 /* delay 15 sec before overwriting, so all clients can see end */
10315 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10317 serverMoves = fopen(appData.serverMovesName, "w");
10321 gameMode = BeginningOfGame;
10323 if(appData.icsActive) gameInfo.variant = VariantNormal;
10324 currentMove = forwardMostMove = backwardMostMove = 0;
10325 InitPosition(redraw);
10326 for (i = 0; i < MAX_MOVES; i++) {
10327 if (commentList[i] != NULL) {
10328 free(commentList[i]);
10329 commentList[i] = NULL;
10333 timeRemaining[0][0] = whiteTimeRemaining;
10334 timeRemaining[1][0] = blackTimeRemaining;
10336 if (first.pr == NULL) {
10337 StartChessProgram(&first);
10340 InitChessProgram(&first, startedFromSetupPosition);
10343 DisplayMessage("", "");
10344 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10345 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10352 if (!AutoPlayOneMove())
10354 if (matchMode || appData.timeDelay == 0)
10356 if (appData.timeDelay < 0)
10358 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10367 int fromX, fromY, toX, toY;
10369 if (appData.debugMode) {
10370 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10373 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10376 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10377 pvInfoList[currentMove].depth = programStats.depth;
10378 pvInfoList[currentMove].score = programStats.score;
10379 pvInfoList[currentMove].time = 0;
10380 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10383 if (currentMove >= forwardMostMove) {
10384 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10385 gameMode = EditGame;
10388 /* [AS] Clear current move marker at the end of a game */
10389 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10394 toX = moveList[currentMove][2] - AAA;
10395 toY = moveList[currentMove][3] - ONE;
10397 if (moveList[currentMove][1] == '@') {
10398 if (appData.highlightLastMove) {
10399 SetHighlights(-1, -1, toX, toY);
10402 fromX = moveList[currentMove][0] - AAA;
10403 fromY = moveList[currentMove][1] - ONE;
10405 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10407 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10409 if (appData.highlightLastMove) {
10410 SetHighlights(fromX, fromY, toX, toY);
10413 DisplayMove(currentMove);
10414 SendMoveToProgram(currentMove++, &first);
10415 DisplayBothClocks();
10416 DrawPosition(FALSE, boards[currentMove]);
10417 // [HGM] PV info: always display, routine tests if empty
10418 DisplayComment(currentMove - 1, commentList[currentMove]);
10424 LoadGameOneMove(readAhead)
10425 ChessMove readAhead;
10427 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10428 char promoChar = NULLCHAR;
10429 ChessMove moveType;
10430 char move[MSG_SIZ];
10433 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10434 gameMode != AnalyzeMode && gameMode != Training) {
10439 yyboardindex = forwardMostMove;
10440 if (readAhead != EndOfFile) {
10441 moveType = readAhead;
10443 if (gameFileFP == NULL)
10445 moveType = (ChessMove) Myylex();
10449 switch (moveType) {
10451 if (appData.debugMode)
10452 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10455 /* append the comment but don't display it */
10456 AppendComment(currentMove, p, FALSE);
10459 case WhiteCapturesEnPassant:
10460 case BlackCapturesEnPassant:
10461 case WhitePromotion:
10462 case BlackPromotion:
10463 case WhiteNonPromotion:
10464 case BlackNonPromotion:
10466 case WhiteKingSideCastle:
10467 case WhiteQueenSideCastle:
10468 case BlackKingSideCastle:
10469 case BlackQueenSideCastle:
10470 case WhiteKingSideCastleWild:
10471 case WhiteQueenSideCastleWild:
10472 case BlackKingSideCastleWild:
10473 case BlackQueenSideCastleWild:
10475 case WhiteHSideCastleFR:
10476 case WhiteASideCastleFR:
10477 case BlackHSideCastleFR:
10478 case BlackASideCastleFR:
10480 if (appData.debugMode)
10481 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10482 fromX = currentMoveString[0] - AAA;
10483 fromY = currentMoveString[1] - ONE;
10484 toX = currentMoveString[2] - AAA;
10485 toY = currentMoveString[3] - ONE;
10486 promoChar = currentMoveString[4];
10491 if (appData.debugMode)
10492 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10493 fromX = moveType == WhiteDrop ?
10494 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10495 (int) CharToPiece(ToLower(currentMoveString[0]));
10497 toX = currentMoveString[2] - AAA;
10498 toY = currentMoveString[3] - ONE;
10504 case GameUnfinished:
10505 if (appData.debugMode)
10506 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10507 p = strchr(yy_text, '{');
10508 if (p == NULL) p = strchr(yy_text, '(');
10511 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10513 q = strchr(p, *p == '{' ? '}' : ')');
10514 if (q != NULL) *q = NULLCHAR;
10517 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10518 GameEnds(moveType, p, GE_FILE);
10520 if (cmailMsgLoaded) {
10522 flipView = WhiteOnMove(currentMove);
10523 if (moveType == GameUnfinished) flipView = !flipView;
10524 if (appData.debugMode)
10525 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10530 if (appData.debugMode)
10531 fprintf(debugFP, "Parser hit end of file\n");
10532 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10538 if (WhiteOnMove(currentMove)) {
10539 GameEnds(BlackWins, "Black mates", GE_FILE);
10541 GameEnds(WhiteWins, "White mates", GE_FILE);
10545 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10551 case MoveNumberOne:
10552 if (lastLoadGameStart == GNUChessGame) {
10553 /* GNUChessGames have numbers, but they aren't move numbers */
10554 if (appData.debugMode)
10555 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10556 yy_text, (int) moveType);
10557 return LoadGameOneMove(EndOfFile); /* tail recursion */
10559 /* else fall thru */
10564 /* Reached start of next game in file */
10565 if (appData.debugMode)
10566 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10567 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10573 if (WhiteOnMove(currentMove)) {
10574 GameEnds(BlackWins, "Black mates", GE_FILE);
10576 GameEnds(WhiteWins, "White mates", GE_FILE);
10580 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10586 case PositionDiagram: /* should not happen; ignore */
10587 case ElapsedTime: /* ignore */
10588 case NAG: /* ignore */
10589 if (appData.debugMode)
10590 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10591 yy_text, (int) moveType);
10592 return LoadGameOneMove(EndOfFile); /* tail recursion */
10595 if (appData.testLegality) {
10596 if (appData.debugMode)
10597 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10598 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10599 (forwardMostMove / 2) + 1,
10600 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10601 DisplayError(move, 0);
10604 if (appData.debugMode)
10605 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10606 yy_text, currentMoveString);
10607 fromX = currentMoveString[0] - AAA;
10608 fromY = currentMoveString[1] - ONE;
10609 toX = currentMoveString[2] - AAA;
10610 toY = currentMoveString[3] - ONE;
10611 promoChar = currentMoveString[4];
10615 case AmbiguousMove:
10616 if (appData.debugMode)
10617 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10618 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10619 (forwardMostMove / 2) + 1,
10620 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10621 DisplayError(move, 0);
10626 case ImpossibleMove:
10627 if (appData.debugMode)
10628 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10629 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10630 (forwardMostMove / 2) + 1,
10631 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10632 DisplayError(move, 0);
10638 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10639 DrawPosition(FALSE, boards[currentMove]);
10640 DisplayBothClocks();
10641 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10642 DisplayComment(currentMove - 1, commentList[currentMove]);
10644 (void) StopLoadGameTimer();
10646 cmailOldMove = forwardMostMove;
10649 /* currentMoveString is set as a side-effect of yylex */
10651 thinkOutput[0] = NULLCHAR;
10652 MakeMove(fromX, fromY, toX, toY, promoChar);
10653 currentMove = forwardMostMove;
10658 /* Load the nth game from the given file */
10660 LoadGameFromFile(filename, n, title, useList)
10664 /*Boolean*/ int useList;
10669 if (strcmp(filename, "-") == 0) {
10673 f = fopen(filename, "rb");
10675 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10676 DisplayError(buf, errno);
10680 if (fseek(f, 0, 0) == -1) {
10681 /* f is not seekable; probably a pipe */
10684 if (useList && n == 0) {
10685 int error = GameListBuild(f);
10687 DisplayError(_("Cannot build game list"), error);
10688 } else if (!ListEmpty(&gameList) &&
10689 ((ListGame *) gameList.tailPred)->number > 1) {
10690 GameListPopUp(f, title);
10697 return LoadGame(f, n, title, FALSE);
10702 MakeRegisteredMove()
10704 int fromX, fromY, toX, toY;
10706 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10707 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10710 if (appData.debugMode)
10711 fprintf(debugFP, "Restoring %s for game %d\n",
10712 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10714 thinkOutput[0] = NULLCHAR;
10715 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10716 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10717 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10718 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10719 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10720 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10721 MakeMove(fromX, fromY, toX, toY, promoChar);
10722 ShowMove(fromX, fromY, toX, toY);
10724 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10731 if (WhiteOnMove(currentMove)) {
10732 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10734 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10739 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10746 if (WhiteOnMove(currentMove)) {
10747 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10749 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10754 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10765 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10767 CmailLoadGame(f, gameNumber, title, useList)
10775 if (gameNumber > nCmailGames) {
10776 DisplayError(_("No more games in this message"), 0);
10779 if (f == lastLoadGameFP) {
10780 int offset = gameNumber - lastLoadGameNumber;
10782 cmailMsg[0] = NULLCHAR;
10783 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10784 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10785 nCmailMovesRegistered--;
10787 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10788 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10789 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10792 if (! RegisterMove()) return FALSE;
10796 retVal = LoadGame(f, gameNumber, title, useList);
10798 /* Make move registered during previous look at this game, if any */
10799 MakeRegisteredMove();
10801 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10802 commentList[currentMove]
10803 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10804 DisplayComment(currentMove - 1, commentList[currentMove]);
10810 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10815 int gameNumber = lastLoadGameNumber + offset;
10816 if (lastLoadGameFP == NULL) {
10817 DisplayError(_("No game has been loaded yet"), 0);
10820 if (gameNumber <= 0) {
10821 DisplayError(_("Can't back up any further"), 0);
10824 if (cmailMsgLoaded) {
10825 return CmailLoadGame(lastLoadGameFP, gameNumber,
10826 lastLoadGameTitle, lastLoadGameUseList);
10828 return LoadGame(lastLoadGameFP, gameNumber,
10829 lastLoadGameTitle, lastLoadGameUseList);
10835 /* Load the nth game from open file f */
10837 LoadGame(f, gameNumber, title, useList)
10845 int gn = gameNumber;
10846 ListGame *lg = NULL;
10847 int numPGNTags = 0;
10849 GameMode oldGameMode;
10850 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10852 if (appData.debugMode)
10853 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10855 if (gameMode == Training )
10856 SetTrainingModeOff();
10858 oldGameMode = gameMode;
10859 if (gameMode != BeginningOfGame) {
10860 Reset(FALSE, TRUE);
10864 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10865 fclose(lastLoadGameFP);
10869 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10872 fseek(f, lg->offset, 0);
10873 GameListHighlight(gameNumber);
10877 DisplayError(_("Game number out of range"), 0);
10882 if (fseek(f, 0, 0) == -1) {
10883 if (f == lastLoadGameFP ?
10884 gameNumber == lastLoadGameNumber + 1 :
10888 DisplayError(_("Can't seek on game file"), 0);
10893 lastLoadGameFP = f;
10894 lastLoadGameNumber = gameNumber;
10895 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10896 lastLoadGameUseList = useList;
10900 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10901 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10902 lg->gameInfo.black);
10904 } else if (*title != NULLCHAR) {
10905 if (gameNumber > 1) {
10906 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10909 DisplayTitle(title);
10913 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10914 gameMode = PlayFromGameFile;
10918 currentMove = forwardMostMove = backwardMostMove = 0;
10919 CopyBoard(boards[0], initialPosition);
10923 * Skip the first gn-1 games in the file.
10924 * Also skip over anything that precedes an identifiable
10925 * start of game marker, to avoid being confused by
10926 * garbage at the start of the file. Currently
10927 * recognized start of game markers are the move number "1",
10928 * the pattern "gnuchess .* game", the pattern
10929 * "^[#;%] [^ ]* game file", and a PGN tag block.
10930 * A game that starts with one of the latter two patterns
10931 * will also have a move number 1, possibly
10932 * following a position diagram.
10933 * 5-4-02: Let's try being more lenient and allowing a game to
10934 * start with an unnumbered move. Does that break anything?
10936 cm = lastLoadGameStart = EndOfFile;
10938 yyboardindex = forwardMostMove;
10939 cm = (ChessMove) Myylex();
10942 if (cmailMsgLoaded) {
10943 nCmailGames = CMAIL_MAX_GAMES - gn;
10946 DisplayError(_("Game not found in file"), 0);
10953 lastLoadGameStart = cm;
10956 case MoveNumberOne:
10957 switch (lastLoadGameStart) {
10962 case MoveNumberOne:
10964 gn--; /* count this game */
10965 lastLoadGameStart = cm;
10974 switch (lastLoadGameStart) {
10977 case MoveNumberOne:
10979 gn--; /* count this game */
10980 lastLoadGameStart = cm;
10983 lastLoadGameStart = cm; /* game counted already */
10991 yyboardindex = forwardMostMove;
10992 cm = (ChessMove) Myylex();
10993 } while (cm == PGNTag || cm == Comment);
11000 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11001 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11002 != CMAIL_OLD_RESULT) {
11004 cmailResult[ CMAIL_MAX_GAMES
11005 - gn - 1] = CMAIL_OLD_RESULT;
11011 /* Only a NormalMove can be at the start of a game
11012 * without a position diagram. */
11013 if (lastLoadGameStart == EndOfFile ) {
11015 lastLoadGameStart = MoveNumberOne;
11024 if (appData.debugMode)
11025 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11027 if (cm == XBoardGame) {
11028 /* Skip any header junk before position diagram and/or move 1 */
11030 yyboardindex = forwardMostMove;
11031 cm = (ChessMove) Myylex();
11033 if (cm == EndOfFile ||
11034 cm == GNUChessGame || cm == XBoardGame) {
11035 /* Empty game; pretend end-of-file and handle later */
11040 if (cm == MoveNumberOne || cm == PositionDiagram ||
11041 cm == PGNTag || cm == Comment)
11044 } else if (cm == GNUChessGame) {
11045 if (gameInfo.event != NULL) {
11046 free(gameInfo.event);
11048 gameInfo.event = StrSave(yy_text);
11051 startedFromSetupPosition = FALSE;
11052 while (cm == PGNTag) {
11053 if (appData.debugMode)
11054 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11055 err = ParsePGNTag(yy_text, &gameInfo);
11056 if (!err) numPGNTags++;
11058 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11059 if(gameInfo.variant != oldVariant) {
11060 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11061 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11062 InitPosition(TRUE);
11063 oldVariant = gameInfo.variant;
11064 if (appData.debugMode)
11065 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11069 if (gameInfo.fen != NULL) {
11070 Board initial_position;
11071 startedFromSetupPosition = TRUE;
11072 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11074 DisplayError(_("Bad FEN position in file"), 0);
11077 CopyBoard(boards[0], initial_position);
11078 if (blackPlaysFirst) {
11079 currentMove = forwardMostMove = backwardMostMove = 1;
11080 CopyBoard(boards[1], initial_position);
11081 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11082 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11083 timeRemaining[0][1] = whiteTimeRemaining;
11084 timeRemaining[1][1] = blackTimeRemaining;
11085 if (commentList[0] != NULL) {
11086 commentList[1] = commentList[0];
11087 commentList[0] = NULL;
11090 currentMove = forwardMostMove = backwardMostMove = 0;
11092 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11094 initialRulePlies = FENrulePlies;
11095 for( i=0; i< nrCastlingRights; i++ )
11096 initialRights[i] = initial_position[CASTLING][i];
11098 yyboardindex = forwardMostMove;
11099 free(gameInfo.fen);
11100 gameInfo.fen = NULL;
11103 yyboardindex = forwardMostMove;
11104 cm = (ChessMove) Myylex();
11106 /* Handle comments interspersed among the tags */
11107 while (cm == Comment) {
11109 if (appData.debugMode)
11110 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11112 AppendComment(currentMove, p, FALSE);
11113 yyboardindex = forwardMostMove;
11114 cm = (ChessMove) Myylex();
11118 /* don't rely on existence of Event tag since if game was
11119 * pasted from clipboard the Event tag may not exist
11121 if (numPGNTags > 0){
11123 if (gameInfo.variant == VariantNormal) {
11124 VariantClass v = StringToVariant(gameInfo.event);
11125 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11126 if(v < VariantShogi) gameInfo.variant = v;
11129 if( appData.autoDisplayTags ) {
11130 tags = PGNTags(&gameInfo);
11131 TagsPopUp(tags, CmailMsg());
11136 /* Make something up, but don't display it now */
11141 if (cm == PositionDiagram) {
11144 Board initial_position;
11146 if (appData.debugMode)
11147 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11149 if (!startedFromSetupPosition) {
11151 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11152 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11163 initial_position[i][j++] = CharToPiece(*p);
11166 while (*p == ' ' || *p == '\t' ||
11167 *p == '\n' || *p == '\r') p++;
11169 if (strncmp(p, "black", strlen("black"))==0)
11170 blackPlaysFirst = TRUE;
11172 blackPlaysFirst = FALSE;
11173 startedFromSetupPosition = TRUE;
11175 CopyBoard(boards[0], initial_position);
11176 if (blackPlaysFirst) {
11177 currentMove = forwardMostMove = backwardMostMove = 1;
11178 CopyBoard(boards[1], initial_position);
11179 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11180 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11181 timeRemaining[0][1] = whiteTimeRemaining;
11182 timeRemaining[1][1] = blackTimeRemaining;
11183 if (commentList[0] != NULL) {
11184 commentList[1] = commentList[0];
11185 commentList[0] = NULL;
11188 currentMove = forwardMostMove = backwardMostMove = 0;
11191 yyboardindex = forwardMostMove;
11192 cm = (ChessMove) Myylex();
11195 if (first.pr == NoProc) {
11196 StartChessProgram(&first);
11198 InitChessProgram(&first, FALSE);
11199 SendToProgram("force\n", &first);
11200 if (startedFromSetupPosition) {
11201 SendBoard(&first, forwardMostMove);
11202 if (appData.debugMode) {
11203 fprintf(debugFP, "Load Game\n");
11205 DisplayBothClocks();
11208 /* [HGM] server: flag to write setup moves in broadcast file as one */
11209 loadFlag = appData.suppressLoadMoves;
11211 while (cm == Comment) {
11213 if (appData.debugMode)
11214 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11216 AppendComment(currentMove, p, FALSE);
11217 yyboardindex = forwardMostMove;
11218 cm = (ChessMove) Myylex();
11221 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11222 cm == WhiteWins || cm == BlackWins ||
11223 cm == GameIsDrawn || cm == GameUnfinished) {
11224 DisplayMessage("", _("No moves in game"));
11225 if (cmailMsgLoaded) {
11226 if (appData.debugMode)
11227 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11231 DrawPosition(FALSE, boards[currentMove]);
11232 DisplayBothClocks();
11233 gameMode = EditGame;
11240 // [HGM] PV info: routine tests if comment empty
11241 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11242 DisplayComment(currentMove - 1, commentList[currentMove]);
11244 if (!matchMode && appData.timeDelay != 0)
11245 DrawPosition(FALSE, boards[currentMove]);
11247 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11248 programStats.ok_to_send = 1;
11251 /* if the first token after the PGN tags is a move
11252 * and not move number 1, retrieve it from the parser
11254 if (cm != MoveNumberOne)
11255 LoadGameOneMove(cm);
11257 /* load the remaining moves from the file */
11258 while (LoadGameOneMove(EndOfFile)) {
11259 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11260 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11263 /* rewind to the start of the game */
11264 currentMove = backwardMostMove;
11266 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11268 if (oldGameMode == AnalyzeFile ||
11269 oldGameMode == AnalyzeMode) {
11270 AnalyzeFileEvent();
11273 if (matchMode || appData.timeDelay == 0) {
11275 gameMode = EditGame;
11277 } else if (appData.timeDelay > 0) {
11278 AutoPlayGameLoop();
11281 if (appData.debugMode)
11282 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11284 loadFlag = 0; /* [HGM] true game starts */
11288 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11290 ReloadPosition(offset)
11293 int positionNumber = lastLoadPositionNumber + offset;
11294 if (lastLoadPositionFP == NULL) {
11295 DisplayError(_("No position has been loaded yet"), 0);
11298 if (positionNumber <= 0) {
11299 DisplayError(_("Can't back up any further"), 0);
11302 return LoadPosition(lastLoadPositionFP, positionNumber,
11303 lastLoadPositionTitle);
11306 /* Load the nth position from the given file */
11308 LoadPositionFromFile(filename, n, title)
11316 if (strcmp(filename, "-") == 0) {
11317 return LoadPosition(stdin, n, "stdin");
11319 f = fopen(filename, "rb");
11321 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11322 DisplayError(buf, errno);
11325 return LoadPosition(f, n, title);
11330 /* Load the nth position from the given open file, and close it */
11332 LoadPosition(f, positionNumber, title)
11334 int positionNumber;
11337 char *p, line[MSG_SIZ];
11338 Board initial_position;
11339 int i, j, fenMode, pn;
11341 if (gameMode == Training )
11342 SetTrainingModeOff();
11344 if (gameMode != BeginningOfGame) {
11345 Reset(FALSE, TRUE);
11347 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11348 fclose(lastLoadPositionFP);
11350 if (positionNumber == 0) positionNumber = 1;
11351 lastLoadPositionFP = f;
11352 lastLoadPositionNumber = positionNumber;
11353 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11354 if (first.pr == NoProc) {
11355 StartChessProgram(&first);
11356 InitChessProgram(&first, FALSE);
11358 pn = positionNumber;
11359 if (positionNumber < 0) {
11360 /* Negative position number means to seek to that byte offset */
11361 if (fseek(f, -positionNumber, 0) == -1) {
11362 DisplayError(_("Can't seek on position file"), 0);
11367 if (fseek(f, 0, 0) == -1) {
11368 if (f == lastLoadPositionFP ?
11369 positionNumber == lastLoadPositionNumber + 1 :
11370 positionNumber == 1) {
11373 DisplayError(_("Can't seek on position file"), 0);
11378 /* See if this file is FEN or old-style xboard */
11379 if (fgets(line, MSG_SIZ, f) == NULL) {
11380 DisplayError(_("Position not found in file"), 0);
11383 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11384 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11387 if (fenMode || line[0] == '#') pn--;
11389 /* skip positions before number pn */
11390 if (fgets(line, MSG_SIZ, f) == NULL) {
11392 DisplayError(_("Position not found in file"), 0);
11395 if (fenMode || line[0] == '#') pn--;
11400 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11401 DisplayError(_("Bad FEN position in file"), 0);
11405 (void) fgets(line, MSG_SIZ, f);
11406 (void) fgets(line, MSG_SIZ, f);
11408 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11409 (void) fgets(line, MSG_SIZ, f);
11410 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11413 initial_position[i][j++] = CharToPiece(*p);
11417 blackPlaysFirst = FALSE;
11419 (void) fgets(line, MSG_SIZ, f);
11420 if (strncmp(line, "black", strlen("black"))==0)
11421 blackPlaysFirst = TRUE;
11424 startedFromSetupPosition = TRUE;
11426 SendToProgram("force\n", &first);
11427 CopyBoard(boards[0], initial_position);
11428 if (blackPlaysFirst) {
11429 currentMove = forwardMostMove = backwardMostMove = 1;
11430 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11431 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11432 CopyBoard(boards[1], initial_position);
11433 DisplayMessage("", _("Black to play"));
11435 currentMove = forwardMostMove = backwardMostMove = 0;
11436 DisplayMessage("", _("White to play"));
11438 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11439 SendBoard(&first, forwardMostMove);
11440 if (appData.debugMode) {
11442 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11443 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11444 fprintf(debugFP, "Load Position\n");
11447 if (positionNumber > 1) {
11448 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11449 DisplayTitle(line);
11451 DisplayTitle(title);
11453 gameMode = EditGame;
11456 timeRemaining[0][1] = whiteTimeRemaining;
11457 timeRemaining[1][1] = blackTimeRemaining;
11458 DrawPosition(FALSE, boards[currentMove]);
11465 CopyPlayerNameIntoFileName(dest, src)
11468 while (*src != NULLCHAR && *src != ',') {
11473 *(*dest)++ = *src++;
11478 char *DefaultFileName(ext)
11481 static char def[MSG_SIZ];
11484 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11486 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11488 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11490 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11497 /* Save the current game to the given file */
11499 SaveGameToFile(filename, append)
11507 if (strcmp(filename, "-") == 0) {
11508 return SaveGame(stdout, 0, NULL);
11510 f = fopen(filename, append ? "a" : "w");
11512 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11513 DisplayError(buf, errno);
11516 safeStrCpy(buf, lastMsg, MSG_SIZ);
11517 DisplayMessage(_("Waiting for access to save file"), "");
11518 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11519 DisplayMessage(_("Saving game"), "");
11520 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11521 result = SaveGame(f, 0, NULL);
11522 DisplayMessage(buf, "");
11532 static char buf[MSG_SIZ];
11535 p = strchr(str, ' ');
11536 if (p == NULL) return str;
11537 strncpy(buf, str, p - str);
11538 buf[p - str] = NULLCHAR;
11542 #define PGN_MAX_LINE 75
11544 #define PGN_SIDE_WHITE 0
11545 #define PGN_SIDE_BLACK 1
11548 static int FindFirstMoveOutOfBook( int side )
11552 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11553 int index = backwardMostMove;
11554 int has_book_hit = 0;
11556 if( (index % 2) != side ) {
11560 while( index < forwardMostMove ) {
11561 /* Check to see if engine is in book */
11562 int depth = pvInfoList[index].depth;
11563 int score = pvInfoList[index].score;
11569 else if( score == 0 && depth == 63 ) {
11570 in_book = 1; /* Zappa */
11572 else if( score == 2 && depth == 99 ) {
11573 in_book = 1; /* Abrok */
11576 has_book_hit += in_book;
11592 void GetOutOfBookInfo( char * buf )
11596 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11598 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11599 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11603 if( oob[0] >= 0 || oob[1] >= 0 ) {
11604 for( i=0; i<2; i++ ) {
11608 if( i > 0 && oob[0] >= 0 ) {
11609 strcat( buf, " " );
11612 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11613 sprintf( buf+strlen(buf), "%s%.2f",
11614 pvInfoList[idx].score >= 0 ? "+" : "",
11615 pvInfoList[idx].score / 100.0 );
11621 /* Save game in PGN style and close the file */
11626 int i, offset, linelen, newblock;
11630 int movelen, numlen, blank;
11631 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11633 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11635 tm = time((time_t *) NULL);
11637 PrintPGNTags(f, &gameInfo);
11639 if (backwardMostMove > 0 || startedFromSetupPosition) {
11640 char *fen = PositionToFEN(backwardMostMove, NULL);
11641 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11642 fprintf(f, "\n{--------------\n");
11643 PrintPosition(f, backwardMostMove);
11644 fprintf(f, "--------------}\n");
11648 /* [AS] Out of book annotation */
11649 if( appData.saveOutOfBookInfo ) {
11652 GetOutOfBookInfo( buf );
11654 if( buf[0] != '\0' ) {
11655 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11662 i = backwardMostMove;
11666 while (i < forwardMostMove) {
11667 /* Print comments preceding this move */
11668 if (commentList[i] != NULL) {
11669 if (linelen > 0) fprintf(f, "\n");
11670 fprintf(f, "%s", commentList[i]);
11675 /* Format move number */
11677 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11680 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11682 numtext[0] = NULLCHAR;
11684 numlen = strlen(numtext);
11687 /* Print move number */
11688 blank = linelen > 0 && numlen > 0;
11689 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11698 fprintf(f, "%s", numtext);
11702 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11703 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11706 blank = linelen > 0 && movelen > 0;
11707 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11716 fprintf(f, "%s", move_buffer);
11717 linelen += movelen;
11719 /* [AS] Add PV info if present */
11720 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11721 /* [HGM] add time */
11722 char buf[MSG_SIZ]; int seconds;
11724 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11730 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11733 seconds = (seconds + 4)/10; // round to full seconds
11735 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11737 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11740 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11741 pvInfoList[i].score >= 0 ? "+" : "",
11742 pvInfoList[i].score / 100.0,
11743 pvInfoList[i].depth,
11746 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11748 /* Print score/depth */
11749 blank = linelen > 0 && movelen > 0;
11750 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11759 fprintf(f, "%s", move_buffer);
11760 linelen += movelen;
11766 /* Start a new line */
11767 if (linelen > 0) fprintf(f, "\n");
11769 /* Print comments after last move */
11770 if (commentList[i] != NULL) {
11771 fprintf(f, "%s\n", commentList[i]);
11775 if (gameInfo.resultDetails != NULL &&
11776 gameInfo.resultDetails[0] != NULLCHAR) {
11777 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11778 PGNResult(gameInfo.result));
11780 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11784 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11788 /* Save game in old style and close the file */
11790 SaveGameOldStyle(f)
11796 tm = time((time_t *) NULL);
11798 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11801 if (backwardMostMove > 0 || startedFromSetupPosition) {
11802 fprintf(f, "\n[--------------\n");
11803 PrintPosition(f, backwardMostMove);
11804 fprintf(f, "--------------]\n");
11809 i = backwardMostMove;
11810 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11812 while (i < forwardMostMove) {
11813 if (commentList[i] != NULL) {
11814 fprintf(f, "[%s]\n", commentList[i]);
11817 if ((i % 2) == 1) {
11818 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11821 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11823 if (commentList[i] != NULL) {
11827 if (i >= forwardMostMove) {
11831 fprintf(f, "%s\n", parseList[i]);
11836 if (commentList[i] != NULL) {
11837 fprintf(f, "[%s]\n", commentList[i]);
11840 /* This isn't really the old style, but it's close enough */
11841 if (gameInfo.resultDetails != NULL &&
11842 gameInfo.resultDetails[0] != NULLCHAR) {
11843 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11844 gameInfo.resultDetails);
11846 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11853 /* Save the current game to open file f and close the file */
11855 SaveGame(f, dummy, dummy2)
11860 if (gameMode == EditPosition) EditPositionDone(TRUE);
11861 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11862 if (appData.oldSaveStyle)
11863 return SaveGameOldStyle(f);
11865 return SaveGamePGN(f);
11868 /* Save the current position to the given file */
11870 SavePositionToFile(filename)
11876 if (strcmp(filename, "-") == 0) {
11877 return SavePosition(stdout, 0, NULL);
11879 f = fopen(filename, "a");
11881 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11882 DisplayError(buf, errno);
11885 safeStrCpy(buf, lastMsg, MSG_SIZ);
11886 DisplayMessage(_("Waiting for access to save file"), "");
11887 flock(fileno(f), LOCK_EX); // [HGM] lock
11888 DisplayMessage(_("Saving position"), "");
11889 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11890 SavePosition(f, 0, NULL);
11891 DisplayMessage(buf, "");
11897 /* Save the current position to the given open file and close the file */
11899 SavePosition(f, dummy, dummy2)
11907 if (gameMode == EditPosition) EditPositionDone(TRUE);
11908 if (appData.oldSaveStyle) {
11909 tm = time((time_t *) NULL);
11911 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11913 fprintf(f, "[--------------\n");
11914 PrintPosition(f, currentMove);
11915 fprintf(f, "--------------]\n");
11917 fen = PositionToFEN(currentMove, NULL);
11918 fprintf(f, "%s\n", fen);
11926 ReloadCmailMsgEvent(unregister)
11930 static char *inFilename = NULL;
11931 static char *outFilename;
11933 struct stat inbuf, outbuf;
11936 /* Any registered moves are unregistered if unregister is set, */
11937 /* i.e. invoked by the signal handler */
11939 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11940 cmailMoveRegistered[i] = FALSE;
11941 if (cmailCommentList[i] != NULL) {
11942 free(cmailCommentList[i]);
11943 cmailCommentList[i] = NULL;
11946 nCmailMovesRegistered = 0;
11949 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11950 cmailResult[i] = CMAIL_NOT_RESULT;
11954 if (inFilename == NULL) {
11955 /* Because the filenames are static they only get malloced once */
11956 /* and they never get freed */
11957 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11958 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11960 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11961 sprintf(outFilename, "%s.out", appData.cmailGameName);
11964 status = stat(outFilename, &outbuf);
11966 cmailMailedMove = FALSE;
11968 status = stat(inFilename, &inbuf);
11969 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11972 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11973 counts the games, notes how each one terminated, etc.
11975 It would be nice to remove this kludge and instead gather all
11976 the information while building the game list. (And to keep it
11977 in the game list nodes instead of having a bunch of fixed-size
11978 parallel arrays.) Note this will require getting each game's
11979 termination from the PGN tags, as the game list builder does
11980 not process the game moves. --mann
11982 cmailMsgLoaded = TRUE;
11983 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11985 /* Load first game in the file or popup game menu */
11986 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11988 #endif /* !WIN32 */
11996 char string[MSG_SIZ];
11998 if ( cmailMailedMove
11999 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12000 return TRUE; /* Allow free viewing */
12003 /* Unregister move to ensure that we don't leave RegisterMove */
12004 /* with the move registered when the conditions for registering no */
12006 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12007 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12008 nCmailMovesRegistered --;
12010 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12012 free(cmailCommentList[lastLoadGameNumber - 1]);
12013 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12017 if (cmailOldMove == -1) {
12018 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12022 if (currentMove > cmailOldMove + 1) {
12023 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12027 if (currentMove < cmailOldMove) {
12028 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12032 if (forwardMostMove > currentMove) {
12033 /* Silently truncate extra moves */
12037 if ( (currentMove == cmailOldMove + 1)
12038 || ( (currentMove == cmailOldMove)
12039 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12040 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12041 if (gameInfo.result != GameUnfinished) {
12042 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12045 if (commentList[currentMove] != NULL) {
12046 cmailCommentList[lastLoadGameNumber - 1]
12047 = StrSave(commentList[currentMove]);
12049 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12051 if (appData.debugMode)
12052 fprintf(debugFP, "Saving %s for game %d\n",
12053 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12055 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12057 f = fopen(string, "w");
12058 if (appData.oldSaveStyle) {
12059 SaveGameOldStyle(f); /* also closes the file */
12061 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12062 f = fopen(string, "w");
12063 SavePosition(f, 0, NULL); /* also closes the file */
12065 fprintf(f, "{--------------\n");
12066 PrintPosition(f, currentMove);
12067 fprintf(f, "--------------}\n\n");
12069 SaveGame(f, 0, NULL); /* also closes the file*/
12072 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12073 nCmailMovesRegistered ++;
12074 } else if (nCmailGames == 1) {
12075 DisplayError(_("You have not made a move yet"), 0);
12086 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12087 FILE *commandOutput;
12088 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12089 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12095 if (! cmailMsgLoaded) {
12096 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12100 if (nCmailGames == nCmailResults) {
12101 DisplayError(_("No unfinished games"), 0);
12105 #if CMAIL_PROHIBIT_REMAIL
12106 if (cmailMailedMove) {
12107 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);
12108 DisplayError(msg, 0);
12113 if (! (cmailMailedMove || RegisterMove())) return;
12115 if ( cmailMailedMove
12116 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12117 snprintf(string, MSG_SIZ, partCommandString,
12118 appData.debugMode ? " -v" : "", appData.cmailGameName);
12119 commandOutput = popen(string, "r");
12121 if (commandOutput == NULL) {
12122 DisplayError(_("Failed to invoke cmail"), 0);
12124 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12125 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12127 if (nBuffers > 1) {
12128 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12129 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12130 nBytes = MSG_SIZ - 1;
12132 (void) memcpy(msg, buffer, nBytes);
12134 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12136 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12137 cmailMailedMove = TRUE; /* Prevent >1 moves */
12140 for (i = 0; i < nCmailGames; i ++) {
12141 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12146 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12148 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12150 appData.cmailGameName,
12152 LoadGameFromFile(buffer, 1, buffer, FALSE);
12153 cmailMsgLoaded = FALSE;
12157 DisplayInformation(msg);
12158 pclose(commandOutput);
12161 if ((*cmailMsg) != '\0') {
12162 DisplayInformation(cmailMsg);
12167 #endif /* !WIN32 */
12176 int prependComma = 0;
12178 char string[MSG_SIZ]; /* Space for game-list */
12181 if (!cmailMsgLoaded) return "";
12183 if (cmailMailedMove) {
12184 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12186 /* Create a list of games left */
12187 snprintf(string, MSG_SIZ, "[");
12188 for (i = 0; i < nCmailGames; i ++) {
12189 if (! ( cmailMoveRegistered[i]
12190 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12191 if (prependComma) {
12192 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12194 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12198 strcat(string, number);
12201 strcat(string, "]");
12203 if (nCmailMovesRegistered + nCmailResults == 0) {
12204 switch (nCmailGames) {
12206 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12210 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12214 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12219 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12221 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12226 if (nCmailResults == nCmailGames) {
12227 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12229 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12234 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12246 if (gameMode == Training)
12247 SetTrainingModeOff();
12250 cmailMsgLoaded = FALSE;
12251 if (appData.icsActive) {
12252 SendToICS(ics_prefix);
12253 SendToICS("refresh\n");
12263 /* Give up on clean exit */
12267 /* Keep trying for clean exit */
12271 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12273 if (telnetISR != NULL) {
12274 RemoveInputSource(telnetISR);
12276 if (icsPR != NoProc) {
12277 DestroyChildProcess(icsPR, TRUE);
12280 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12281 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12283 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12284 /* make sure this other one finishes before killing it! */
12285 if(endingGame) { int count = 0;
12286 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12287 while(endingGame && count++ < 10) DoSleep(1);
12288 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12291 /* Kill off chess programs */
12292 if (first.pr != NoProc) {
12295 DoSleep( appData.delayBeforeQuit );
12296 SendToProgram("quit\n", &first);
12297 DoSleep( appData.delayAfterQuit );
12298 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12300 if (second.pr != NoProc) {
12301 DoSleep( appData.delayBeforeQuit );
12302 SendToProgram("quit\n", &second);
12303 DoSleep( appData.delayAfterQuit );
12304 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12306 if (first.isr != NULL) {
12307 RemoveInputSource(first.isr);
12309 if (second.isr != NULL) {
12310 RemoveInputSource(second.isr);
12313 ShutDownFrontEnd();
12320 if (appData.debugMode)
12321 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12325 if (gameMode == MachinePlaysWhite ||
12326 gameMode == MachinePlaysBlack) {
12329 DisplayBothClocks();
12331 if (gameMode == PlayFromGameFile) {
12332 if (appData.timeDelay >= 0)
12333 AutoPlayGameLoop();
12334 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12335 Reset(FALSE, TRUE);
12336 SendToICS(ics_prefix);
12337 SendToICS("refresh\n");
12338 } else if (currentMove < forwardMostMove) {
12339 ForwardInner(forwardMostMove);
12341 pauseExamInvalid = FALSE;
12343 switch (gameMode) {
12347 pauseExamForwardMostMove = forwardMostMove;
12348 pauseExamInvalid = FALSE;
12351 case IcsPlayingWhite:
12352 case IcsPlayingBlack:
12356 case PlayFromGameFile:
12357 (void) StopLoadGameTimer();
12361 case BeginningOfGame:
12362 if (appData.icsActive) return;
12363 /* else fall through */
12364 case MachinePlaysWhite:
12365 case MachinePlaysBlack:
12366 case TwoMachinesPlay:
12367 if (forwardMostMove == 0)
12368 return; /* don't pause if no one has moved */
12369 if ((gameMode == MachinePlaysWhite &&
12370 !WhiteOnMove(forwardMostMove)) ||
12371 (gameMode == MachinePlaysBlack &&
12372 WhiteOnMove(forwardMostMove))) {
12385 char title[MSG_SIZ];
12387 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12388 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12390 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12391 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12392 parseList[currentMove - 1]);
12395 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12402 char *tags = PGNTags(&gameInfo);
12404 EditTagsPopUp(tags, NULL);
12411 if (appData.noChessProgram || gameMode == AnalyzeMode)
12414 if (gameMode != AnalyzeFile) {
12415 if (!appData.icsEngineAnalyze) {
12417 if (gameMode != EditGame) return;
12419 ResurrectChessProgram();
12420 SendToProgram("analyze\n", &first);
12421 first.analyzing = TRUE;
12422 /*first.maybeThinking = TRUE;*/
12423 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12424 EngineOutputPopUp();
12426 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12431 StartAnalysisClock();
12432 GetTimeMark(&lastNodeCountTime);
12439 if (appData.noChessProgram || gameMode == AnalyzeFile)
12442 if (gameMode != AnalyzeMode) {
12444 if (gameMode != EditGame) return;
12445 ResurrectChessProgram();
12446 SendToProgram("analyze\n", &first);
12447 first.analyzing = TRUE;
12448 /*first.maybeThinking = TRUE;*/
12449 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12450 EngineOutputPopUp();
12452 gameMode = AnalyzeFile;
12457 StartAnalysisClock();
12458 GetTimeMark(&lastNodeCountTime);
12463 MachineWhiteEvent()
12466 char *bookHit = NULL;
12468 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12472 if (gameMode == PlayFromGameFile ||
12473 gameMode == TwoMachinesPlay ||
12474 gameMode == Training ||
12475 gameMode == AnalyzeMode ||
12476 gameMode == EndOfGame)
12479 if (gameMode == EditPosition)
12480 EditPositionDone(TRUE);
12482 if (!WhiteOnMove(currentMove)) {
12483 DisplayError(_("It is not White's turn"), 0);
12487 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12490 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12491 gameMode == AnalyzeFile)
12494 ResurrectChessProgram(); /* in case it isn't running */
12495 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12496 gameMode = MachinePlaysWhite;
12499 gameMode = MachinePlaysWhite;
12503 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12505 if (first.sendName) {
12506 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12507 SendToProgram(buf, &first);
12509 if (first.sendTime) {
12510 if (first.useColors) {
12511 SendToProgram("black\n", &first); /*gnu kludge*/
12513 SendTimeRemaining(&first, TRUE);
12515 if (first.useColors) {
12516 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12518 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12519 SetMachineThinkingEnables();
12520 first.maybeThinking = TRUE;
12524 if (appData.autoFlipView && !flipView) {
12525 flipView = !flipView;
12526 DrawPosition(FALSE, NULL);
12527 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12530 if(bookHit) { // [HGM] book: simulate book reply
12531 static char bookMove[MSG_SIZ]; // a bit generous?
12533 programStats.nodes = programStats.depth = programStats.time =
12534 programStats.score = programStats.got_only_move = 0;
12535 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12537 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12538 strcat(bookMove, bookHit);
12539 HandleMachineMove(bookMove, &first);
12544 MachineBlackEvent()
12547 char *bookHit = NULL;
12549 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12553 if (gameMode == PlayFromGameFile ||
12554 gameMode == TwoMachinesPlay ||
12555 gameMode == Training ||
12556 gameMode == AnalyzeMode ||
12557 gameMode == EndOfGame)
12560 if (gameMode == EditPosition)
12561 EditPositionDone(TRUE);
12563 if (WhiteOnMove(currentMove)) {
12564 DisplayError(_("It is not Black's turn"), 0);
12568 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12571 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12572 gameMode == AnalyzeFile)
12575 ResurrectChessProgram(); /* in case it isn't running */
12576 gameMode = MachinePlaysBlack;
12580 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12582 if (first.sendName) {
12583 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12584 SendToProgram(buf, &first);
12586 if (first.sendTime) {
12587 if (first.useColors) {
12588 SendToProgram("white\n", &first); /*gnu kludge*/
12590 SendTimeRemaining(&first, FALSE);
12592 if (first.useColors) {
12593 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12595 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12596 SetMachineThinkingEnables();
12597 first.maybeThinking = TRUE;
12600 if (appData.autoFlipView && flipView) {
12601 flipView = !flipView;
12602 DrawPosition(FALSE, NULL);
12603 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12605 if(bookHit) { // [HGM] book: simulate book reply
12606 static char bookMove[MSG_SIZ]; // a bit generous?
12608 programStats.nodes = programStats.depth = programStats.time =
12609 programStats.score = programStats.got_only_move = 0;
12610 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12612 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12613 strcat(bookMove, bookHit);
12614 HandleMachineMove(bookMove, &first);
12620 DisplayTwoMachinesTitle()
12623 if (appData.matchGames > 0) {
12624 if (first.twoMachinesColor[0] == 'w') {
12625 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12626 gameInfo.white, gameInfo.black,
12627 first.matchWins, second.matchWins,
12628 matchGame - 1 - (first.matchWins + second.matchWins));
12630 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12631 gameInfo.white, gameInfo.black,
12632 second.matchWins, first.matchWins,
12633 matchGame - 1 - (first.matchWins + second.matchWins));
12636 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12642 SettingsMenuIfReady()
12644 if (second.lastPing != second.lastPong) {
12645 DisplayMessage("", _("Waiting for second chess program"));
12646 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12650 DisplayMessage("", "");
12651 SettingsPopUp(&second);
12655 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12658 if (cps->pr == NULL) {
12659 StartChessProgram(cps);
12660 if (cps->protocolVersion == 1) {
12663 /* kludge: allow timeout for initial "feature" command */
12665 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12666 DisplayMessage("", buf);
12667 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12675 TwoMachinesEvent P((void))
12679 ChessProgramState *onmove;
12680 char *bookHit = NULL;
12681 static int stalling = 0;
12685 if (appData.noChessProgram) return;
12687 switch (gameMode) {
12688 case TwoMachinesPlay:
12690 case MachinePlaysWhite:
12691 case MachinePlaysBlack:
12692 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12693 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12697 case BeginningOfGame:
12698 case PlayFromGameFile:
12701 if (gameMode != EditGame) return;
12704 EditPositionDone(TRUE);
12715 // forwardMostMove = currentMove;
12716 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12718 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12720 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12721 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12722 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12726 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12727 SendToProgram("force\n", &second);
12729 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12732 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12733 if(appData.matchPause>10000 || appData.matchPause<10)
12734 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12735 wait = SubtractTimeMarks(&now, &pauseStart);
12736 if(wait < appData.matchPause) {
12737 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12741 DisplayMessage("", "");
12742 if (startedFromSetupPosition) {
12743 SendBoard(&second, backwardMostMove);
12744 if (appData.debugMode) {
12745 fprintf(debugFP, "Two Machines\n");
12748 for (i = backwardMostMove; i < forwardMostMove; i++) {
12749 SendMoveToProgram(i, &second);
12752 gameMode = TwoMachinesPlay;
12756 DisplayTwoMachinesTitle();
12758 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12763 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12764 SendToProgram(first.computerString, &first);
12765 if (first.sendName) {
12766 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12767 SendToProgram(buf, &first);
12769 SendToProgram(second.computerString, &second);
12770 if (second.sendName) {
12771 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12772 SendToProgram(buf, &second);
12776 if (!first.sendTime || !second.sendTime) {
12777 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12778 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12780 if (onmove->sendTime) {
12781 if (onmove->useColors) {
12782 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12784 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12786 if (onmove->useColors) {
12787 SendToProgram(onmove->twoMachinesColor, onmove);
12789 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12790 // SendToProgram("go\n", onmove);
12791 onmove->maybeThinking = TRUE;
12792 SetMachineThinkingEnables();
12796 if(bookHit) { // [HGM] book: simulate book reply
12797 static char bookMove[MSG_SIZ]; // a bit generous?
12799 programStats.nodes = programStats.depth = programStats.time =
12800 programStats.score = programStats.got_only_move = 0;
12801 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12803 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12804 strcat(bookMove, bookHit);
12805 savedMessage = bookMove; // args for deferred call
12806 savedState = onmove;
12807 ScheduleDelayedEvent(DeferredBookMove, 1);
12814 if (gameMode == Training) {
12815 SetTrainingModeOff();
12816 gameMode = PlayFromGameFile;
12817 DisplayMessage("", _("Training mode off"));
12819 gameMode = Training;
12820 animateTraining = appData.animate;
12822 /* make sure we are not already at the end of the game */
12823 if (currentMove < forwardMostMove) {
12824 SetTrainingModeOn();
12825 DisplayMessage("", _("Training mode on"));
12827 gameMode = PlayFromGameFile;
12828 DisplayError(_("Already at end of game"), 0);
12837 if (!appData.icsActive) return;
12838 switch (gameMode) {
12839 case IcsPlayingWhite:
12840 case IcsPlayingBlack:
12843 case BeginningOfGame:
12851 EditPositionDone(TRUE);
12864 gameMode = IcsIdle;
12875 switch (gameMode) {
12877 SetTrainingModeOff();
12879 case MachinePlaysWhite:
12880 case MachinePlaysBlack:
12881 case BeginningOfGame:
12882 SendToProgram("force\n", &first);
12883 SetUserThinkingEnables();
12885 case PlayFromGameFile:
12886 (void) StopLoadGameTimer();
12887 if (gameFileFP != NULL) {
12892 EditPositionDone(TRUE);
12897 SendToProgram("force\n", &first);
12899 case TwoMachinesPlay:
12900 GameEnds(EndOfFile, NULL, GE_PLAYER);
12901 ResurrectChessProgram();
12902 SetUserThinkingEnables();
12905 ResurrectChessProgram();
12907 case IcsPlayingBlack:
12908 case IcsPlayingWhite:
12909 DisplayError(_("Warning: You are still playing a game"), 0);
12912 DisplayError(_("Warning: You are still observing a game"), 0);
12915 DisplayError(_("Warning: You are still examining a game"), 0);
12926 first.offeredDraw = second.offeredDraw = 0;
12928 if (gameMode == PlayFromGameFile) {
12929 whiteTimeRemaining = timeRemaining[0][currentMove];
12930 blackTimeRemaining = timeRemaining[1][currentMove];
12934 if (gameMode == MachinePlaysWhite ||
12935 gameMode == MachinePlaysBlack ||
12936 gameMode == TwoMachinesPlay ||
12937 gameMode == EndOfGame) {
12938 i = forwardMostMove;
12939 while (i > currentMove) {
12940 SendToProgram("undo\n", &first);
12943 whiteTimeRemaining = timeRemaining[0][currentMove];
12944 blackTimeRemaining = timeRemaining[1][currentMove];
12945 DisplayBothClocks();
12946 if (whiteFlag || blackFlag) {
12947 whiteFlag = blackFlag = 0;
12952 gameMode = EditGame;
12959 EditPositionEvent()
12961 if (gameMode == EditPosition) {
12967 if (gameMode != EditGame) return;
12969 gameMode = EditPosition;
12972 if (currentMove > 0)
12973 CopyBoard(boards[0], boards[currentMove]);
12975 blackPlaysFirst = !WhiteOnMove(currentMove);
12977 currentMove = forwardMostMove = backwardMostMove = 0;
12978 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12985 /* [DM] icsEngineAnalyze - possible call from other functions */
12986 if (appData.icsEngineAnalyze) {
12987 appData.icsEngineAnalyze = FALSE;
12989 DisplayMessage("",_("Close ICS engine analyze..."));
12991 if (first.analysisSupport && first.analyzing) {
12992 SendToProgram("exit\n", &first);
12993 first.analyzing = FALSE;
12995 thinkOutput[0] = NULLCHAR;
12999 EditPositionDone(Boolean fakeRights)
13001 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13003 startedFromSetupPosition = TRUE;
13004 InitChessProgram(&first, FALSE);
13005 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13006 boards[0][EP_STATUS] = EP_NONE;
13007 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13008 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13009 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13010 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13011 } else boards[0][CASTLING][2] = NoRights;
13012 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13013 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13014 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13015 } else boards[0][CASTLING][5] = NoRights;
13017 SendToProgram("force\n", &first);
13018 if (blackPlaysFirst) {
13019 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13020 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13021 currentMove = forwardMostMove = backwardMostMove = 1;
13022 CopyBoard(boards[1], boards[0]);
13024 currentMove = forwardMostMove = backwardMostMove = 0;
13026 SendBoard(&first, forwardMostMove);
13027 if (appData.debugMode) {
13028 fprintf(debugFP, "EditPosDone\n");
13031 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13032 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13033 gameMode = EditGame;
13035 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13036 ClearHighlights(); /* [AS] */
13039 /* Pause for `ms' milliseconds */
13040 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13050 } while (SubtractTimeMarks(&m2, &m1) < ms);
13053 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13055 SendMultiLineToICS(buf)
13058 char temp[MSG_SIZ+1], *p;
13065 strncpy(temp, buf, len);
13070 if (*p == '\n' || *p == '\r')
13075 strcat(temp, "\n");
13077 SendToPlayer(temp, strlen(temp));
13081 SetWhiteToPlayEvent()
13083 if (gameMode == EditPosition) {
13084 blackPlaysFirst = FALSE;
13085 DisplayBothClocks(); /* works because currentMove is 0 */
13086 } else if (gameMode == IcsExamining) {
13087 SendToICS(ics_prefix);
13088 SendToICS("tomove white\n");
13093 SetBlackToPlayEvent()
13095 if (gameMode == EditPosition) {
13096 blackPlaysFirst = TRUE;
13097 currentMove = 1; /* kludge */
13098 DisplayBothClocks();
13100 } else if (gameMode == IcsExamining) {
13101 SendToICS(ics_prefix);
13102 SendToICS("tomove black\n");
13107 EditPositionMenuEvent(selection, x, y)
13108 ChessSquare selection;
13112 ChessSquare piece = boards[0][y][x];
13114 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13116 switch (selection) {
13118 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13119 SendToICS(ics_prefix);
13120 SendToICS("bsetup clear\n");
13121 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13122 SendToICS(ics_prefix);
13123 SendToICS("clearboard\n");
13125 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13126 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13127 for (y = 0; y < BOARD_HEIGHT; y++) {
13128 if (gameMode == IcsExamining) {
13129 if (boards[currentMove][y][x] != EmptySquare) {
13130 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13135 boards[0][y][x] = p;
13140 if (gameMode == EditPosition) {
13141 DrawPosition(FALSE, boards[0]);
13146 SetWhiteToPlayEvent();
13150 SetBlackToPlayEvent();
13154 if (gameMode == IcsExamining) {
13155 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13156 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13159 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13160 if(x == BOARD_LEFT-2) {
13161 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13162 boards[0][y][1] = 0;
13164 if(x == BOARD_RGHT+1) {
13165 if(y >= gameInfo.holdingsSize) break;
13166 boards[0][y][BOARD_WIDTH-2] = 0;
13169 boards[0][y][x] = EmptySquare;
13170 DrawPosition(FALSE, boards[0]);
13175 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13176 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13177 selection = (ChessSquare) (PROMOTED piece);
13178 } else if(piece == EmptySquare) selection = WhiteSilver;
13179 else selection = (ChessSquare)((int)piece - 1);
13183 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13184 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13185 selection = (ChessSquare) (DEMOTED piece);
13186 } else if(piece == EmptySquare) selection = BlackSilver;
13187 else selection = (ChessSquare)((int)piece + 1);
13192 if(gameInfo.variant == VariantShatranj ||
13193 gameInfo.variant == VariantXiangqi ||
13194 gameInfo.variant == VariantCourier ||
13195 gameInfo.variant == VariantMakruk )
13196 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13201 if(gameInfo.variant == VariantXiangqi)
13202 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13203 if(gameInfo.variant == VariantKnightmate)
13204 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13207 if (gameMode == IcsExamining) {
13208 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13209 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13210 PieceToChar(selection), AAA + x, ONE + y);
13213 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13215 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13216 n = PieceToNumber(selection - BlackPawn);
13217 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13218 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13219 boards[0][BOARD_HEIGHT-1-n][1]++;
13221 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13222 n = PieceToNumber(selection);
13223 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13224 boards[0][n][BOARD_WIDTH-1] = selection;
13225 boards[0][n][BOARD_WIDTH-2]++;
13228 boards[0][y][x] = selection;
13229 DrawPosition(TRUE, boards[0]);
13237 DropMenuEvent(selection, x, y)
13238 ChessSquare selection;
13241 ChessMove moveType;
13243 switch (gameMode) {
13244 case IcsPlayingWhite:
13245 case MachinePlaysBlack:
13246 if (!WhiteOnMove(currentMove)) {
13247 DisplayMoveError(_("It is Black's turn"));
13250 moveType = WhiteDrop;
13252 case IcsPlayingBlack:
13253 case MachinePlaysWhite:
13254 if (WhiteOnMove(currentMove)) {
13255 DisplayMoveError(_("It is White's turn"));
13258 moveType = BlackDrop;
13261 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13267 if (moveType == BlackDrop && selection < BlackPawn) {
13268 selection = (ChessSquare) ((int) selection
13269 + (int) BlackPawn - (int) WhitePawn);
13271 if (boards[currentMove][y][x] != EmptySquare) {
13272 DisplayMoveError(_("That square is occupied"));
13276 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13282 /* Accept a pending offer of any kind from opponent */
13284 if (appData.icsActive) {
13285 SendToICS(ics_prefix);
13286 SendToICS("accept\n");
13287 } else if (cmailMsgLoaded) {
13288 if (currentMove == cmailOldMove &&
13289 commentList[cmailOldMove] != NULL &&
13290 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13291 "Black offers a draw" : "White offers a draw")) {
13293 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13294 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13296 DisplayError(_("There is no pending offer on this move"), 0);
13297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13300 /* Not used for offers from chess program */
13307 /* Decline a pending offer of any kind from opponent */
13309 if (appData.icsActive) {
13310 SendToICS(ics_prefix);
13311 SendToICS("decline\n");
13312 } else if (cmailMsgLoaded) {
13313 if (currentMove == cmailOldMove &&
13314 commentList[cmailOldMove] != NULL &&
13315 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13316 "Black offers a draw" : "White offers a draw")) {
13318 AppendComment(cmailOldMove, "Draw declined", TRUE);
13319 DisplayComment(cmailOldMove - 1, "Draw declined");
13322 DisplayError(_("There is no pending offer on this move"), 0);
13325 /* Not used for offers from chess program */
13332 /* Issue ICS rematch command */
13333 if (appData.icsActive) {
13334 SendToICS(ics_prefix);
13335 SendToICS("rematch\n");
13342 /* Call your opponent's flag (claim a win on time) */
13343 if (appData.icsActive) {
13344 SendToICS(ics_prefix);
13345 SendToICS("flag\n");
13347 switch (gameMode) {
13350 case MachinePlaysWhite:
13353 GameEnds(GameIsDrawn, "Both players ran out of time",
13356 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13358 DisplayError(_("Your opponent is not out of time"), 0);
13361 case MachinePlaysBlack:
13364 GameEnds(GameIsDrawn, "Both players ran out of time",
13367 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13369 DisplayError(_("Your opponent is not out of time"), 0);
13377 ClockClick(int which)
13378 { // [HGM] code moved to back-end from winboard.c
13379 if(which) { // black clock
13380 if (gameMode == EditPosition || gameMode == IcsExamining) {
13381 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13382 SetBlackToPlayEvent();
13383 } else if (gameMode == EditGame || shiftKey) {
13384 AdjustClock(which, -1);
13385 } else if (gameMode == IcsPlayingWhite ||
13386 gameMode == MachinePlaysBlack) {
13389 } else { // white clock
13390 if (gameMode == EditPosition || gameMode == IcsExamining) {
13391 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13392 SetWhiteToPlayEvent();
13393 } else if (gameMode == EditGame || shiftKey) {
13394 AdjustClock(which, -1);
13395 } else if (gameMode == IcsPlayingBlack ||
13396 gameMode == MachinePlaysWhite) {
13405 /* Offer draw or accept pending draw offer from opponent */
13407 if (appData.icsActive) {
13408 /* Note: tournament rules require draw offers to be
13409 made after you make your move but before you punch
13410 your clock. Currently ICS doesn't let you do that;
13411 instead, you immediately punch your clock after making
13412 a move, but you can offer a draw at any time. */
13414 SendToICS(ics_prefix);
13415 SendToICS("draw\n");
13416 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13417 } else if (cmailMsgLoaded) {
13418 if (currentMove == cmailOldMove &&
13419 commentList[cmailOldMove] != NULL &&
13420 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13421 "Black offers a draw" : "White offers a draw")) {
13422 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13423 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13424 } else if (currentMove == cmailOldMove + 1) {
13425 char *offer = WhiteOnMove(cmailOldMove) ?
13426 "White offers a draw" : "Black offers a draw";
13427 AppendComment(currentMove, offer, TRUE);
13428 DisplayComment(currentMove - 1, offer);
13429 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13431 DisplayError(_("You must make your move before offering a draw"), 0);
13432 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13434 } else if (first.offeredDraw) {
13435 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13437 if (first.sendDrawOffers) {
13438 SendToProgram("draw\n", &first);
13439 userOfferedDraw = TRUE;
13447 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13449 if (appData.icsActive) {
13450 SendToICS(ics_prefix);
13451 SendToICS("adjourn\n");
13453 /* Currently GNU Chess doesn't offer or accept Adjourns */
13461 /* Offer Abort or accept pending Abort offer from opponent */
13463 if (appData.icsActive) {
13464 SendToICS(ics_prefix);
13465 SendToICS("abort\n");
13467 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13474 /* Resign. You can do this even if it's not your turn. */
13476 if (appData.icsActive) {
13477 SendToICS(ics_prefix);
13478 SendToICS("resign\n");
13480 switch (gameMode) {
13481 case MachinePlaysWhite:
13482 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13484 case MachinePlaysBlack:
13485 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13488 if (cmailMsgLoaded) {
13490 if (WhiteOnMove(cmailOldMove)) {
13491 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13493 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13495 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13506 StopObservingEvent()
13508 /* Stop observing current games */
13509 SendToICS(ics_prefix);
13510 SendToICS("unobserve\n");
13514 StopExaminingEvent()
13516 /* Stop observing current game */
13517 SendToICS(ics_prefix);
13518 SendToICS("unexamine\n");
13522 ForwardInner(target)
13527 if (appData.debugMode)
13528 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13529 target, currentMove, forwardMostMove);
13531 if (gameMode == EditPosition)
13534 if (gameMode == PlayFromGameFile && !pausing)
13537 if (gameMode == IcsExamining && pausing)
13538 limit = pauseExamForwardMostMove;
13540 limit = forwardMostMove;
13542 if (target > limit) target = limit;
13544 if (target > 0 && moveList[target - 1][0]) {
13545 int fromX, fromY, toX, toY;
13546 toX = moveList[target - 1][2] - AAA;
13547 toY = moveList[target - 1][3] - ONE;
13548 if (moveList[target - 1][1] == '@') {
13549 if (appData.highlightLastMove) {
13550 SetHighlights(-1, -1, toX, toY);
13553 fromX = moveList[target - 1][0] - AAA;
13554 fromY = moveList[target - 1][1] - ONE;
13555 if (target == currentMove + 1) {
13556 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13558 if (appData.highlightLastMove) {
13559 SetHighlights(fromX, fromY, toX, toY);
13563 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13564 gameMode == Training || gameMode == PlayFromGameFile ||
13565 gameMode == AnalyzeFile) {
13566 while (currentMove < target) {
13567 SendMoveToProgram(currentMove++, &first);
13570 currentMove = target;
13573 if (gameMode == EditGame || gameMode == EndOfGame) {
13574 whiteTimeRemaining = timeRemaining[0][currentMove];
13575 blackTimeRemaining = timeRemaining[1][currentMove];
13577 DisplayBothClocks();
13578 DisplayMove(currentMove - 1);
13579 DrawPosition(FALSE, boards[currentMove]);
13580 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13581 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13582 DisplayComment(currentMove - 1, commentList[currentMove]);
13584 DisplayBook(currentMove);
13591 if (gameMode == IcsExamining && !pausing) {
13592 SendToICS(ics_prefix);
13593 SendToICS("forward\n");
13595 ForwardInner(currentMove + 1);
13602 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13603 /* to optimze, we temporarily turn off analysis mode while we feed
13604 * the remaining moves to the engine. Otherwise we get analysis output
13607 if (first.analysisSupport) {
13608 SendToProgram("exit\nforce\n", &first);
13609 first.analyzing = FALSE;
13613 if (gameMode == IcsExamining && !pausing) {
13614 SendToICS(ics_prefix);
13615 SendToICS("forward 999999\n");
13617 ForwardInner(forwardMostMove);
13620 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13621 /* we have fed all the moves, so reactivate analysis mode */
13622 SendToProgram("analyze\n", &first);
13623 first.analyzing = TRUE;
13624 /*first.maybeThinking = TRUE;*/
13625 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13630 BackwardInner(target)
13633 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13635 if (appData.debugMode)
13636 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13637 target, currentMove, forwardMostMove);
13639 if (gameMode == EditPosition) return;
13640 if (currentMove <= backwardMostMove) {
13642 DrawPosition(full_redraw, boards[currentMove]);
13645 if (gameMode == PlayFromGameFile && !pausing)
13648 if (moveList[target][0]) {
13649 int fromX, fromY, toX, toY;
13650 toX = moveList[target][2] - AAA;
13651 toY = moveList[target][3] - ONE;
13652 if (moveList[target][1] == '@') {
13653 if (appData.highlightLastMove) {
13654 SetHighlights(-1, -1, toX, toY);
13657 fromX = moveList[target][0] - AAA;
13658 fromY = moveList[target][1] - ONE;
13659 if (target == currentMove - 1) {
13660 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13662 if (appData.highlightLastMove) {
13663 SetHighlights(fromX, fromY, toX, toY);
13667 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13668 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13669 while (currentMove > target) {
13670 SendToProgram("undo\n", &first);
13674 currentMove = target;
13677 if (gameMode == EditGame || gameMode == EndOfGame) {
13678 whiteTimeRemaining = timeRemaining[0][currentMove];
13679 blackTimeRemaining = timeRemaining[1][currentMove];
13681 DisplayBothClocks();
13682 DisplayMove(currentMove - 1);
13683 DrawPosition(full_redraw, boards[currentMove]);
13684 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13685 // [HGM] PV info: routine tests if comment empty
13686 DisplayComment(currentMove - 1, commentList[currentMove]);
13687 DisplayBook(currentMove);
13693 if (gameMode == IcsExamining && !pausing) {
13694 SendToICS(ics_prefix);
13695 SendToICS("backward\n");
13697 BackwardInner(currentMove - 1);
13704 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13705 /* to optimize, we temporarily turn off analysis mode while we undo
13706 * all the moves. Otherwise we get analysis output after each undo.
13708 if (first.analysisSupport) {
13709 SendToProgram("exit\nforce\n", &first);
13710 first.analyzing = FALSE;
13714 if (gameMode == IcsExamining && !pausing) {
13715 SendToICS(ics_prefix);
13716 SendToICS("backward 999999\n");
13718 BackwardInner(backwardMostMove);
13721 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13722 /* we have fed all the moves, so reactivate analysis mode */
13723 SendToProgram("analyze\n", &first);
13724 first.analyzing = TRUE;
13725 /*first.maybeThinking = TRUE;*/
13726 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13733 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13734 if (to >= forwardMostMove) to = forwardMostMove;
13735 if (to <= backwardMostMove) to = backwardMostMove;
13736 if (to < currentMove) {
13744 RevertEvent(Boolean annotate)
13746 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13749 if (gameMode != IcsExamining) {
13750 DisplayError(_("You are not examining a game"), 0);
13754 DisplayError(_("You can't revert while pausing"), 0);
13757 SendToICS(ics_prefix);
13758 SendToICS("revert\n");
13764 switch (gameMode) {
13765 case MachinePlaysWhite:
13766 case MachinePlaysBlack:
13767 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13768 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13771 if (forwardMostMove < 2) return;
13772 currentMove = forwardMostMove = forwardMostMove - 2;
13773 whiteTimeRemaining = timeRemaining[0][currentMove];
13774 blackTimeRemaining = timeRemaining[1][currentMove];
13775 DisplayBothClocks();
13776 DisplayMove(currentMove - 1);
13777 ClearHighlights();/*!! could figure this out*/
13778 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13779 SendToProgram("remove\n", &first);
13780 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13783 case BeginningOfGame:
13787 case IcsPlayingWhite:
13788 case IcsPlayingBlack:
13789 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13790 SendToICS(ics_prefix);
13791 SendToICS("takeback 2\n");
13793 SendToICS(ics_prefix);
13794 SendToICS("takeback 1\n");
13803 ChessProgramState *cps;
13805 switch (gameMode) {
13806 case MachinePlaysWhite:
13807 if (!WhiteOnMove(forwardMostMove)) {
13808 DisplayError(_("It is your turn"), 0);
13813 case MachinePlaysBlack:
13814 if (WhiteOnMove(forwardMostMove)) {
13815 DisplayError(_("It is your turn"), 0);
13820 case TwoMachinesPlay:
13821 if (WhiteOnMove(forwardMostMove) ==
13822 (first.twoMachinesColor[0] == 'w')) {
13828 case BeginningOfGame:
13832 SendToProgram("?\n", cps);
13836 TruncateGameEvent()
13839 if (gameMode != EditGame) return;
13846 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13847 if (forwardMostMove > currentMove) {
13848 if (gameInfo.resultDetails != NULL) {
13849 free(gameInfo.resultDetails);
13850 gameInfo.resultDetails = NULL;
13851 gameInfo.result = GameUnfinished;
13853 forwardMostMove = currentMove;
13854 HistorySet(parseList, backwardMostMove, forwardMostMove,
13862 if (appData.noChessProgram) return;
13863 switch (gameMode) {
13864 case MachinePlaysWhite:
13865 if (WhiteOnMove(forwardMostMove)) {
13866 DisplayError(_("Wait until your turn"), 0);
13870 case BeginningOfGame:
13871 case MachinePlaysBlack:
13872 if (!WhiteOnMove(forwardMostMove)) {
13873 DisplayError(_("Wait until your turn"), 0);
13878 DisplayError(_("No hint available"), 0);
13881 SendToProgram("hint\n", &first);
13882 hintRequested = TRUE;
13888 if (appData.noChessProgram) return;
13889 switch (gameMode) {
13890 case MachinePlaysWhite:
13891 if (WhiteOnMove(forwardMostMove)) {
13892 DisplayError(_("Wait until your turn"), 0);
13896 case BeginningOfGame:
13897 case MachinePlaysBlack:
13898 if (!WhiteOnMove(forwardMostMove)) {
13899 DisplayError(_("Wait until your turn"), 0);
13904 EditPositionDone(TRUE);
13906 case TwoMachinesPlay:
13911 SendToProgram("bk\n", &first);
13912 bookOutput[0] = NULLCHAR;
13913 bookRequested = TRUE;
13919 char *tags = PGNTags(&gameInfo);
13920 TagsPopUp(tags, CmailMsg());
13924 /* end button procedures */
13927 PrintPosition(fp, move)
13933 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13934 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13935 char c = PieceToChar(boards[move][i][j]);
13936 fputc(c == 'x' ? '.' : c, fp);
13937 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13940 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13941 fprintf(fp, "white to play\n");
13943 fprintf(fp, "black to play\n");
13950 if (gameInfo.white != NULL) {
13951 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13957 /* Find last component of program's own name, using some heuristics */
13959 TidyProgramName(prog, host, buf)
13960 char *prog, *host, buf[MSG_SIZ];
13963 int local = (strcmp(host, "localhost") == 0);
13964 while (!local && (p = strchr(prog, ';')) != NULL) {
13966 while (*p == ' ') p++;
13969 if (*prog == '"' || *prog == '\'') {
13970 q = strchr(prog + 1, *prog);
13972 q = strchr(prog, ' ');
13974 if (q == NULL) q = prog + strlen(prog);
13976 while (p >= prog && *p != '/' && *p != '\\') p--;
13978 if(p == prog && *p == '"') p++;
13979 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13980 memcpy(buf, p, q - p);
13981 buf[q - p] = NULLCHAR;
13989 TimeControlTagValue()
13992 if (!appData.clockMode) {
13993 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13994 } else if (movesPerSession > 0) {
13995 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13996 } else if (timeIncrement == 0) {
13997 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13999 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14001 return StrSave(buf);
14007 /* This routine is used only for certain modes */
14008 VariantClass v = gameInfo.variant;
14009 ChessMove r = GameUnfinished;
14012 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14013 r = gameInfo.result;
14014 p = gameInfo.resultDetails;
14015 gameInfo.resultDetails = NULL;
14017 ClearGameInfo(&gameInfo);
14018 gameInfo.variant = v;
14020 switch (gameMode) {
14021 case MachinePlaysWhite:
14022 gameInfo.event = StrSave( appData.pgnEventHeader );
14023 gameInfo.site = StrSave(HostName());
14024 gameInfo.date = PGNDate();
14025 gameInfo.round = StrSave("-");
14026 gameInfo.white = StrSave(first.tidy);
14027 gameInfo.black = StrSave(UserName());
14028 gameInfo.timeControl = TimeControlTagValue();
14031 case MachinePlaysBlack:
14032 gameInfo.event = StrSave( appData.pgnEventHeader );
14033 gameInfo.site = StrSave(HostName());
14034 gameInfo.date = PGNDate();
14035 gameInfo.round = StrSave("-");
14036 gameInfo.white = StrSave(UserName());
14037 gameInfo.black = StrSave(first.tidy);
14038 gameInfo.timeControl = TimeControlTagValue();
14041 case TwoMachinesPlay:
14042 gameInfo.event = StrSave( appData.pgnEventHeader );
14043 gameInfo.site = StrSave(HostName());
14044 gameInfo.date = PGNDate();
14047 snprintf(buf, MSG_SIZ, "%d", roundNr);
14048 gameInfo.round = StrSave(buf);
14050 gameInfo.round = StrSave("-");
14052 if (first.twoMachinesColor[0] == 'w') {
14053 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14054 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14056 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14057 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14059 gameInfo.timeControl = TimeControlTagValue();
14063 gameInfo.event = StrSave("Edited game");
14064 gameInfo.site = StrSave(HostName());
14065 gameInfo.date = PGNDate();
14066 gameInfo.round = StrSave("-");
14067 gameInfo.white = StrSave("-");
14068 gameInfo.black = StrSave("-");
14069 gameInfo.result = r;
14070 gameInfo.resultDetails = p;
14074 gameInfo.event = StrSave("Edited position");
14075 gameInfo.site = StrSave(HostName());
14076 gameInfo.date = PGNDate();
14077 gameInfo.round = StrSave("-");
14078 gameInfo.white = StrSave("-");
14079 gameInfo.black = StrSave("-");
14082 case IcsPlayingWhite:
14083 case IcsPlayingBlack:
14088 case PlayFromGameFile:
14089 gameInfo.event = StrSave("Game from non-PGN file");
14090 gameInfo.site = StrSave(HostName());
14091 gameInfo.date = PGNDate();
14092 gameInfo.round = StrSave("-");
14093 gameInfo.white = StrSave("?");
14094 gameInfo.black = StrSave("?");
14103 ReplaceComment(index, text)
14111 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14112 pvInfoList[index-1].depth == len &&
14113 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14114 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14115 while (*text == '\n') text++;
14116 len = strlen(text);
14117 while (len > 0 && text[len - 1] == '\n') len--;
14119 if (commentList[index] != NULL)
14120 free(commentList[index]);
14123 commentList[index] = NULL;
14126 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14127 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14128 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14129 commentList[index] = (char *) malloc(len + 2);
14130 strncpy(commentList[index], text, len);
14131 commentList[index][len] = '\n';
14132 commentList[index][len + 1] = NULLCHAR;
14134 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14136 commentList[index] = (char *) malloc(len + 7);
14137 safeStrCpy(commentList[index], "{\n", 3);
14138 safeStrCpy(commentList[index]+2, text, len+1);
14139 commentList[index][len+2] = NULLCHAR;
14140 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14141 strcat(commentList[index], "\n}\n");
14155 if (ch == '\r') continue;
14157 } while (ch != '\0');
14161 AppendComment(index, text, addBraces)
14164 Boolean addBraces; // [HGM] braces: tells if we should add {}
14169 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14170 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14173 while (*text == '\n') text++;
14174 len = strlen(text);
14175 while (len > 0 && text[len - 1] == '\n') len--;
14177 if (len == 0) return;
14179 if (commentList[index] != NULL) {
14180 old = commentList[index];
14181 oldlen = strlen(old);
14182 while(commentList[index][oldlen-1] == '\n')
14183 commentList[index][--oldlen] = NULLCHAR;
14184 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14185 safeStrCpy(commentList[index], old, oldlen + len + 6);
14187 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14188 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14189 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14190 while (*text == '\n') { text++; len--; }
14191 commentList[index][--oldlen] = NULLCHAR;
14193 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14194 else strcat(commentList[index], "\n");
14195 strcat(commentList[index], text);
14196 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14197 else strcat(commentList[index], "\n");
14199 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14201 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14202 else commentList[index][0] = NULLCHAR;
14203 strcat(commentList[index], text);
14204 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14205 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14209 static char * FindStr( char * text, char * sub_text )
14211 char * result = strstr( text, sub_text );
14213 if( result != NULL ) {
14214 result += strlen( sub_text );
14220 /* [AS] Try to extract PV info from PGN comment */
14221 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14222 char *GetInfoFromComment( int index, char * text )
14224 char * sep = text, *p;
14226 if( text != NULL && index > 0 ) {
14229 int time = -1, sec = 0, deci;
14230 char * s_eval = FindStr( text, "[%eval " );
14231 char * s_emt = FindStr( text, "[%emt " );
14233 if( s_eval != NULL || s_emt != NULL ) {
14237 if( s_eval != NULL ) {
14238 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14242 if( delim != ']' ) {
14247 if( s_emt != NULL ) {
14252 /* We expect something like: [+|-]nnn.nn/dd */
14255 if(*text != '{') return text; // [HGM] braces: must be normal comment
14257 sep = strchr( text, '/' );
14258 if( sep == NULL || sep < (text+4) ) {
14263 if(p[1] == '(') { // comment starts with PV
14264 p = strchr(p, ')'); // locate end of PV
14265 if(p == NULL || sep < p+5) return text;
14266 // at this point we have something like "{(.*) +0.23/6 ..."
14267 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14268 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14269 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14271 time = -1; sec = -1; deci = -1;
14272 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14273 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14274 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14275 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14279 if( score_lo < 0 || score_lo >= 100 ) {
14283 if(sec >= 0) time = 600*time + 10*sec; else
14284 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14286 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14288 /* [HGM] PV time: now locate end of PV info */
14289 while( *++sep >= '0' && *sep <= '9'); // strip depth
14291 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14293 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14295 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14296 while(*sep == ' ') sep++;
14307 pvInfoList[index-1].depth = depth;
14308 pvInfoList[index-1].score = score;
14309 pvInfoList[index-1].time = 10*time; // centi-sec
14310 if(*sep == '}') *sep = 0; else *--sep = '{';
14311 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14317 SendToProgram(message, cps)
14319 ChessProgramState *cps;
14321 int count, outCount, error;
14324 if (cps->pr == NULL) return;
14327 if (appData.debugMode) {
14330 fprintf(debugFP, "%ld >%-6s: %s",
14331 SubtractTimeMarks(&now, &programStartTime),
14332 cps->which, message);
14335 count = strlen(message);
14336 outCount = OutputToProcess(cps->pr, message, count, &error);
14337 if (outCount < count && !exiting
14338 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14339 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14340 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14341 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14342 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14343 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14344 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14345 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14347 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14348 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14349 gameInfo.result = res;
14351 gameInfo.resultDetails = StrSave(buf);
14353 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14354 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14359 ReceiveFromProgram(isr, closure, message, count, error)
14360 InputSourceRef isr;
14368 ChessProgramState *cps = (ChessProgramState *)closure;
14370 if (isr != cps->isr) return; /* Killed intentionally */
14373 RemoveInputSource(cps->isr);
14374 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14375 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14376 _(cps->which), cps->program);
14377 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14378 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14379 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14380 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14381 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14383 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14384 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14385 gameInfo.result = res;
14387 gameInfo.resultDetails = StrSave(buf);
14389 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14390 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14392 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14393 _(cps->which), cps->program);
14394 RemoveInputSource(cps->isr);
14396 /* [AS] Program is misbehaving badly... kill it */
14397 if( count == -2 ) {
14398 DestroyChildProcess( cps->pr, 9 );
14402 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14407 if ((end_str = strchr(message, '\r')) != NULL)
14408 *end_str = NULLCHAR;
14409 if ((end_str = strchr(message, '\n')) != NULL)
14410 *end_str = NULLCHAR;
14412 if (appData.debugMode) {
14413 TimeMark now; int print = 1;
14414 char *quote = ""; char c; int i;
14416 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14417 char start = message[0];
14418 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14419 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14420 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14421 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14422 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14423 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14424 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14425 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14426 sscanf(message, "hint: %c", &c)!=1 &&
14427 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14428 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14429 print = (appData.engineComments >= 2);
14431 message[0] = start; // restore original message
14435 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14436 SubtractTimeMarks(&now, &programStartTime), cps->which,
14442 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14443 if (appData.icsEngineAnalyze) {
14444 if (strstr(message, "whisper") != NULL ||
14445 strstr(message, "kibitz") != NULL ||
14446 strstr(message, "tellics") != NULL) return;
14449 HandleMachineMove(message, cps);
14454 SendTimeControl(cps, mps, tc, inc, sd, st)
14455 ChessProgramState *cps;
14456 int mps, inc, sd, st;
14462 if( timeControl_2 > 0 ) {
14463 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14464 tc = timeControl_2;
14467 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14468 inc /= cps->timeOdds;
14469 st /= cps->timeOdds;
14471 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14474 /* Set exact time per move, normally using st command */
14475 if (cps->stKludge) {
14476 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14478 if (seconds == 0) {
14479 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14481 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14484 snprintf(buf, MSG_SIZ, "st %d\n", st);
14487 /* Set conventional or incremental time control, using level command */
14488 if (seconds == 0) {
14489 /* Note old gnuchess bug -- minutes:seconds used to not work.
14490 Fixed in later versions, but still avoid :seconds
14491 when seconds is 0. */
14492 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14494 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14495 seconds, inc/1000.);
14498 SendToProgram(buf, cps);
14500 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14501 /* Orthogonally, limit search to given depth */
14503 if (cps->sdKludge) {
14504 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14506 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14508 SendToProgram(buf, cps);
14511 if(cps->nps >= 0) { /* [HGM] nps */
14512 if(cps->supportsNPS == FALSE)
14513 cps->nps = -1; // don't use if engine explicitly says not supported!
14515 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14516 SendToProgram(buf, cps);
14521 ChessProgramState *WhitePlayer()
14522 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14524 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14525 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14531 SendTimeRemaining(cps, machineWhite)
14532 ChessProgramState *cps;
14533 int /*boolean*/ machineWhite;
14535 char message[MSG_SIZ];
14538 /* Note: this routine must be called when the clocks are stopped
14539 or when they have *just* been set or switched; otherwise
14540 it will be off by the time since the current tick started.
14542 if (machineWhite) {
14543 time = whiteTimeRemaining / 10;
14544 otime = blackTimeRemaining / 10;
14546 time = blackTimeRemaining / 10;
14547 otime = whiteTimeRemaining / 10;
14549 /* [HGM] translate opponent's time by time-odds factor */
14550 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14551 if (appData.debugMode) {
14552 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14555 if (time <= 0) time = 1;
14556 if (otime <= 0) otime = 1;
14558 snprintf(message, MSG_SIZ, "time %ld\n", time);
14559 SendToProgram(message, cps);
14561 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14562 SendToProgram(message, cps);
14566 BoolFeature(p, name, loc, cps)
14570 ChessProgramState *cps;
14573 int len = strlen(name);
14576 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14578 sscanf(*p, "%d", &val);
14580 while (**p && **p != ' ')
14582 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14583 SendToProgram(buf, cps);
14590 IntFeature(p, name, loc, cps)
14594 ChessProgramState *cps;
14597 int len = strlen(name);
14598 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14600 sscanf(*p, "%d", loc);
14601 while (**p && **p != ' ') (*p)++;
14602 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14603 SendToProgram(buf, cps);
14610 StringFeature(p, name, loc, cps)
14614 ChessProgramState *cps;
14617 int len = strlen(name);
14618 if (strncmp((*p), name, len) == 0
14619 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14621 sscanf(*p, "%[^\"]", loc);
14622 while (**p && **p != '\"') (*p)++;
14623 if (**p == '\"') (*p)++;
14624 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14625 SendToProgram(buf, cps);
14632 ParseOption(Option *opt, ChessProgramState *cps)
14633 // [HGM] options: process the string that defines an engine option, and determine
14634 // name, type, default value, and allowed value range
14636 char *p, *q, buf[MSG_SIZ];
14637 int n, min = (-1)<<31, max = 1<<31, def;
14639 if(p = strstr(opt->name, " -spin ")) {
14640 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14641 if(max < min) max = min; // enforce consistency
14642 if(def < min) def = min;
14643 if(def > max) def = max;
14648 } else if((p = strstr(opt->name, " -slider "))) {
14649 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14650 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14651 if(max < min) max = min; // enforce consistency
14652 if(def < min) def = min;
14653 if(def > max) def = max;
14657 opt->type = Spin; // Slider;
14658 } else if((p = strstr(opt->name, " -string "))) {
14659 opt->textValue = p+9;
14660 opt->type = TextBox;
14661 } else if((p = strstr(opt->name, " -file "))) {
14662 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14663 opt->textValue = p+7;
14664 opt->type = FileName; // FileName;
14665 } else if((p = strstr(opt->name, " -path "))) {
14666 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14667 opt->textValue = p+7;
14668 opt->type = PathName; // PathName;
14669 } else if(p = strstr(opt->name, " -check ")) {
14670 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14671 opt->value = (def != 0);
14672 opt->type = CheckBox;
14673 } else if(p = strstr(opt->name, " -combo ")) {
14674 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14675 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14676 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14677 opt->value = n = 0;
14678 while(q = StrStr(q, " /// ")) {
14679 n++; *q = 0; // count choices, and null-terminate each of them
14681 if(*q == '*') { // remember default, which is marked with * prefix
14685 cps->comboList[cps->comboCnt++] = q;
14687 cps->comboList[cps->comboCnt++] = NULL;
14689 opt->type = ComboBox;
14690 } else if(p = strstr(opt->name, " -button")) {
14691 opt->type = Button;
14692 } else if(p = strstr(opt->name, " -save")) {
14693 opt->type = SaveButton;
14694 } else return FALSE;
14695 *p = 0; // terminate option name
14696 // now look if the command-line options define a setting for this engine option.
14697 if(cps->optionSettings && cps->optionSettings[0])
14698 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14699 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14700 snprintf(buf, MSG_SIZ, "option %s", p);
14701 if(p = strstr(buf, ",")) *p = 0;
14702 if(q = strchr(buf, '=')) switch(opt->type) {
14704 for(n=0; n<opt->max; n++)
14705 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14708 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14712 opt->value = atoi(q+1);
14717 SendToProgram(buf, cps);
14723 FeatureDone(cps, val)
14724 ChessProgramState* cps;
14727 DelayedEventCallback cb = GetDelayedEvent();
14728 if ((cb == InitBackEnd3 && cps == &first) ||
14729 (cb == SettingsMenuIfReady && cps == &second) ||
14730 (cb == LoadEngine) ||
14731 (cb == TwoMachinesEventIfReady)) {
14732 CancelDelayedEvent();
14733 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14735 cps->initDone = val;
14738 /* Parse feature command from engine */
14740 ParseFeatures(args, cps)
14742 ChessProgramState *cps;
14750 while (*p == ' ') p++;
14751 if (*p == NULLCHAR) return;
14753 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14754 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14755 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14756 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14757 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14758 if (BoolFeature(&p, "reuse", &val, cps)) {
14759 /* Engine can disable reuse, but can't enable it if user said no */
14760 if (!val) cps->reuse = FALSE;
14763 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14764 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14765 if (gameMode == TwoMachinesPlay) {
14766 DisplayTwoMachinesTitle();
14772 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14773 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14774 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14775 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14776 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14777 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14778 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14779 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14780 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14781 if (IntFeature(&p, "done", &val, cps)) {
14782 FeatureDone(cps, val);
14785 /* Added by Tord: */
14786 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14787 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14788 /* End of additions by Tord */
14790 /* [HGM] added features: */
14791 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14792 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14793 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14794 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14795 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14796 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14797 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14798 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14799 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14800 SendToProgram(buf, cps);
14803 if(cps->nrOptions >= MAX_OPTIONS) {
14805 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14806 DisplayError(buf, 0);
14810 /* End of additions by HGM */
14812 /* unknown feature: complain and skip */
14814 while (*q && *q != '=') q++;
14815 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14816 SendToProgram(buf, cps);
14822 while (*p && *p != '\"') p++;
14823 if (*p == '\"') p++;
14825 while (*p && *p != ' ') p++;
14833 PeriodicUpdatesEvent(newState)
14836 if (newState == appData.periodicUpdates)
14839 appData.periodicUpdates=newState;
14841 /* Display type changes, so update it now */
14842 // DisplayAnalysis();
14844 /* Get the ball rolling again... */
14846 AnalysisPeriodicEvent(1);
14847 StartAnalysisClock();
14852 PonderNextMoveEvent(newState)
14855 if (newState == appData.ponderNextMove) return;
14856 if (gameMode == EditPosition) EditPositionDone(TRUE);
14858 SendToProgram("hard\n", &first);
14859 if (gameMode == TwoMachinesPlay) {
14860 SendToProgram("hard\n", &second);
14863 SendToProgram("easy\n", &first);
14864 thinkOutput[0] = NULLCHAR;
14865 if (gameMode == TwoMachinesPlay) {
14866 SendToProgram("easy\n", &second);
14869 appData.ponderNextMove = newState;
14873 NewSettingEvent(option, feature, command, value)
14875 int option, value, *feature;
14879 if (gameMode == EditPosition) EditPositionDone(TRUE);
14880 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14881 if(feature == NULL || *feature) SendToProgram(buf, &first);
14882 if (gameMode == TwoMachinesPlay) {
14883 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14888 ShowThinkingEvent()
14889 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14891 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14892 int newState = appData.showThinking
14893 // [HGM] thinking: other features now need thinking output as well
14894 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14896 if (oldState == newState) return;
14897 oldState = newState;
14898 if (gameMode == EditPosition) EditPositionDone(TRUE);
14900 SendToProgram("post\n", &first);
14901 if (gameMode == TwoMachinesPlay) {
14902 SendToProgram("post\n", &second);
14905 SendToProgram("nopost\n", &first);
14906 thinkOutput[0] = NULLCHAR;
14907 if (gameMode == TwoMachinesPlay) {
14908 SendToProgram("nopost\n", &second);
14911 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14915 AskQuestionEvent(title, question, replyPrefix, which)
14916 char *title; char *question; char *replyPrefix; char *which;
14918 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14919 if (pr == NoProc) return;
14920 AskQuestion(title, question, replyPrefix, pr);
14924 TypeInEvent(char firstChar)
14926 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14927 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14928 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14929 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14930 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14931 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14932 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14933 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14934 gameMode == Training) PopUpMoveDialog(firstChar);
14938 TypeInDoneEvent(char *move)
14941 int n, fromX, fromY, toX, toY;
14943 ChessMove moveType;
\r
14946 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14947 EditPositionPasteFEN(move);
\r
14950 // [HGM] movenum: allow move number to be typed in any mode
\r
14951 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14952 ToNrEvent(2*n-1);
\r
14956 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14957 gameMode != Training) {
\r
14958 DisplayMoveError(_("Displayed move is not current"));
\r
14960 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14961 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14962 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14963 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14964 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14965 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14967 DisplayMoveError(_("Could not parse move"));
\r
14973 DisplayMove(moveNumber)
14976 char message[MSG_SIZ];
14978 char cpThinkOutput[MSG_SIZ];
14980 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14982 if (moveNumber == forwardMostMove - 1 ||
14983 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14985 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14987 if (strchr(cpThinkOutput, '\n')) {
14988 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14991 *cpThinkOutput = NULLCHAR;
14994 /* [AS] Hide thinking from human user */
14995 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14996 *cpThinkOutput = NULLCHAR;
14997 if( thinkOutput[0] != NULLCHAR ) {
15000 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15001 cpThinkOutput[i] = '.';
15003 cpThinkOutput[i] = NULLCHAR;
15004 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15008 if (moveNumber == forwardMostMove - 1 &&
15009 gameInfo.resultDetails != NULL) {
15010 if (gameInfo.resultDetails[0] == NULLCHAR) {
15011 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15013 snprintf(res, MSG_SIZ, " {%s} %s",
15014 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15020 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15021 DisplayMessage(res, cpThinkOutput);
15023 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15024 WhiteOnMove(moveNumber) ? " " : ".. ",
15025 parseList[moveNumber], res);
15026 DisplayMessage(message, cpThinkOutput);
15031 DisplayComment(moveNumber, text)
15035 char title[MSG_SIZ];
15036 char buf[8000]; // comment can be long!
15039 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15040 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15042 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15043 WhiteOnMove(moveNumber) ? " " : ".. ",
15044 parseList[moveNumber]);
15046 // [HGM] PV info: display PV info together with (or as) comment
15047 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15048 if(text == NULL) text = "";
15049 score = pvInfoList[moveNumber].score;
15050 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15051 depth, (pvInfoList[moveNumber].time+50)/100, text);
15054 if (text != NULL && (appData.autoDisplayComment || commentUp))
15055 CommentPopUp(title, text);
15058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15059 * might be busy thinking or pondering. It can be omitted if your
15060 * gnuchess is configured to stop thinking immediately on any user
15061 * input. However, that gnuchess feature depends on the FIONREAD
15062 * ioctl, which does not work properly on some flavors of Unix.
15066 ChessProgramState *cps;
15069 if (!cps->useSigint) return;
15070 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15071 switch (gameMode) {
15072 case MachinePlaysWhite:
15073 case MachinePlaysBlack:
15074 case TwoMachinesPlay:
15075 case IcsPlayingWhite:
15076 case IcsPlayingBlack:
15079 /* Skip if we know it isn't thinking */
15080 if (!cps->maybeThinking) return;
15081 if (appData.debugMode)
15082 fprintf(debugFP, "Interrupting %s\n", cps->which);
15083 InterruptChildProcess(cps->pr);
15084 cps->maybeThinking = FALSE;
15089 #endif /*ATTENTION*/
15095 if (whiteTimeRemaining <= 0) {
15098 if (appData.icsActive) {
15099 if (appData.autoCallFlag &&
15100 gameMode == IcsPlayingBlack && !blackFlag) {
15101 SendToICS(ics_prefix);
15102 SendToICS("flag\n");
15106 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15108 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15109 if (appData.autoCallFlag) {
15110 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15117 if (blackTimeRemaining <= 0) {
15120 if (appData.icsActive) {
15121 if (appData.autoCallFlag &&
15122 gameMode == IcsPlayingWhite && !whiteFlag) {
15123 SendToICS(ics_prefix);
15124 SendToICS("flag\n");
15128 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15130 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15131 if (appData.autoCallFlag) {
15132 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15145 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15146 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15149 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15151 if ( !WhiteOnMove(forwardMostMove) ) {
15152 /* White made time control */
15153 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15154 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15155 /* [HGM] time odds: correct new time quota for time odds! */
15156 / WhitePlayer()->timeOdds;
15157 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15159 lastBlack -= blackTimeRemaining;
15160 /* Black made time control */
15161 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15162 / WhitePlayer()->other->timeOdds;
15163 lastWhite = whiteTimeRemaining;
15168 DisplayBothClocks()
15170 int wom = gameMode == EditPosition ?
15171 !blackPlaysFirst : WhiteOnMove(currentMove);
15172 DisplayWhiteClock(whiteTimeRemaining, wom);
15173 DisplayBlackClock(blackTimeRemaining, !wom);
15177 /* Timekeeping seems to be a portability nightmare. I think everyone
15178 has ftime(), but I'm really not sure, so I'm including some ifdefs
15179 to use other calls if you don't. Clocks will be less accurate if
15180 you have neither ftime nor gettimeofday.
15183 /* VS 2008 requires the #include outside of the function */
15184 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15185 #include <sys/timeb.h>
15188 /* Get the current time as a TimeMark */
15193 #if HAVE_GETTIMEOFDAY
15195 struct timeval timeVal;
15196 struct timezone timeZone;
15198 gettimeofday(&timeVal, &timeZone);
15199 tm->sec = (long) timeVal.tv_sec;
15200 tm->ms = (int) (timeVal.tv_usec / 1000L);
15202 #else /*!HAVE_GETTIMEOFDAY*/
15205 // include <sys/timeb.h> / moved to just above start of function
15206 struct timeb timeB;
15209 tm->sec = (long) timeB.time;
15210 tm->ms = (int) timeB.millitm;
15212 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15213 tm->sec = (long) time(NULL);
15219 /* Return the difference in milliseconds between two
15220 time marks. We assume the difference will fit in a long!
15223 SubtractTimeMarks(tm2, tm1)
15224 TimeMark *tm2, *tm1;
15226 return 1000L*(tm2->sec - tm1->sec) +
15227 (long) (tm2->ms - tm1->ms);
15232 * Code to manage the game clocks.
15234 * In tournament play, black starts the clock and then white makes a move.
15235 * We give the human user a slight advantage if he is playing white---the
15236 * clocks don't run until he makes his first move, so it takes zero time.
15237 * Also, we don't account for network lag, so we could get out of sync
15238 * with GNU Chess's clock -- but then, referees are always right.
15241 static TimeMark tickStartTM;
15242 static long intendedTickLength;
15245 NextTickLength(timeRemaining)
15246 long timeRemaining;
15248 long nominalTickLength, nextTickLength;
15250 if (timeRemaining > 0L && timeRemaining <= 10000L)
15251 nominalTickLength = 100L;
15253 nominalTickLength = 1000L;
15254 nextTickLength = timeRemaining % nominalTickLength;
15255 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15257 return nextTickLength;
15260 /* Adjust clock one minute up or down */
15262 AdjustClock(Boolean which, int dir)
15264 if(which) blackTimeRemaining += 60000*dir;
15265 else whiteTimeRemaining += 60000*dir;
15266 DisplayBothClocks();
15269 /* Stop clocks and reset to a fresh time control */
15273 (void) StopClockTimer();
15274 if (appData.icsActive) {
15275 whiteTimeRemaining = blackTimeRemaining = 0;
15276 } else if (searchTime) {
15277 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15278 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15279 } else { /* [HGM] correct new time quote for time odds */
15280 whiteTC = blackTC = fullTimeControlString;
15281 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15282 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15284 if (whiteFlag || blackFlag) {
15286 whiteFlag = blackFlag = FALSE;
15288 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15289 DisplayBothClocks();
15292 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15294 /* Decrement running clock by amount of time that has passed */
15298 long timeRemaining;
15299 long lastTickLength, fudge;
15302 if (!appData.clockMode) return;
15303 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15307 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15309 /* Fudge if we woke up a little too soon */
15310 fudge = intendedTickLength - lastTickLength;
15311 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15313 if (WhiteOnMove(forwardMostMove)) {
15314 if(whiteNPS >= 0) lastTickLength = 0;
15315 timeRemaining = whiteTimeRemaining -= lastTickLength;
15316 if(timeRemaining < 0 && !appData.icsActive) {
15317 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15318 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15319 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15320 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15323 DisplayWhiteClock(whiteTimeRemaining - fudge,
15324 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15326 if(blackNPS >= 0) lastTickLength = 0;
15327 timeRemaining = blackTimeRemaining -= lastTickLength;
15328 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15329 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15331 blackStartMove = forwardMostMove;
15332 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15335 DisplayBlackClock(blackTimeRemaining - fudge,
15336 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15338 if (CheckFlags()) return;
15341 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15342 StartClockTimer(intendedTickLength);
15344 /* if the time remaining has fallen below the alarm threshold, sound the
15345 * alarm. if the alarm has sounded and (due to a takeback or time control
15346 * with increment) the time remaining has increased to a level above the
15347 * threshold, reset the alarm so it can sound again.
15350 if (appData.icsActive && appData.icsAlarm) {
15352 /* make sure we are dealing with the user's clock */
15353 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15354 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15357 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15358 alarmSounded = FALSE;
15359 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15361 alarmSounded = TRUE;
15367 /* A player has just moved, so stop the previously running
15368 clock and (if in clock mode) start the other one.
15369 We redisplay both clocks in case we're in ICS mode, because
15370 ICS gives us an update to both clocks after every move.
15371 Note that this routine is called *after* forwardMostMove
15372 is updated, so the last fractional tick must be subtracted
15373 from the color that is *not* on move now.
15376 SwitchClocks(int newMoveNr)
15378 long lastTickLength;
15380 int flagged = FALSE;
15384 if (StopClockTimer() && appData.clockMode) {
15385 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15386 if (!WhiteOnMove(forwardMostMove)) {
15387 if(blackNPS >= 0) lastTickLength = 0;
15388 blackTimeRemaining -= lastTickLength;
15389 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15390 // if(pvInfoList[forwardMostMove].time == -1)
15391 pvInfoList[forwardMostMove].time = // use GUI time
15392 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15394 if(whiteNPS >= 0) lastTickLength = 0;
15395 whiteTimeRemaining -= lastTickLength;
15396 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15397 // if(pvInfoList[forwardMostMove].time == -1)
15398 pvInfoList[forwardMostMove].time =
15399 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15401 flagged = CheckFlags();
15403 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15404 CheckTimeControl();
15406 if (flagged || !appData.clockMode) return;
15408 switch (gameMode) {
15409 case MachinePlaysBlack:
15410 case MachinePlaysWhite:
15411 case BeginningOfGame:
15412 if (pausing) return;
15416 case PlayFromGameFile:
15424 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15425 if(WhiteOnMove(forwardMostMove))
15426 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15427 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15431 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15432 whiteTimeRemaining : blackTimeRemaining);
15433 StartClockTimer(intendedTickLength);
15437 /* Stop both clocks */
15441 long lastTickLength;
15444 if (!StopClockTimer()) return;
15445 if (!appData.clockMode) return;
15449 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15450 if (WhiteOnMove(forwardMostMove)) {
15451 if(whiteNPS >= 0) lastTickLength = 0;
15452 whiteTimeRemaining -= lastTickLength;
15453 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15455 if(blackNPS >= 0) lastTickLength = 0;
15456 blackTimeRemaining -= lastTickLength;
15457 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15462 /* Start clock of player on move. Time may have been reset, so
15463 if clock is already running, stop and restart it. */
15467 (void) StopClockTimer(); /* in case it was running already */
15468 DisplayBothClocks();
15469 if (CheckFlags()) return;
15471 if (!appData.clockMode) return;
15472 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15474 GetTimeMark(&tickStartTM);
15475 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15476 whiteTimeRemaining : blackTimeRemaining);
15478 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15479 whiteNPS = blackNPS = -1;
15480 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15481 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15482 whiteNPS = first.nps;
15483 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15484 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15485 blackNPS = first.nps;
15486 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15487 whiteNPS = second.nps;
15488 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15489 blackNPS = second.nps;
15490 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15492 StartClockTimer(intendedTickLength);
15499 long second, minute, hour, day;
15501 static char buf[32];
15503 if (ms > 0 && ms <= 9900) {
15504 /* convert milliseconds to tenths, rounding up */
15505 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15507 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15511 /* convert milliseconds to seconds, rounding up */
15512 /* use floating point to avoid strangeness of integer division
15513 with negative dividends on many machines */
15514 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15521 day = second / (60 * 60 * 24);
15522 second = second % (60 * 60 * 24);
15523 hour = second / (60 * 60);
15524 second = second % (60 * 60);
15525 minute = second / 60;
15526 second = second % 60;
15529 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15530 sign, day, hour, minute, second);
15532 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15534 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15541 * This is necessary because some C libraries aren't ANSI C compliant yet.
15544 StrStr(string, match)
15545 char *string, *match;
15549 length = strlen(match);
15551 for (i = strlen(string) - length; i >= 0; i--, string++)
15552 if (!strncmp(match, string, length))
15559 StrCaseStr(string, match)
15560 char *string, *match;
15564 length = strlen(match);
15566 for (i = strlen(string) - length; i >= 0; i--, string++) {
15567 for (j = 0; j < length; j++) {
15568 if (ToLower(match[j]) != ToLower(string[j]))
15571 if (j == length) return string;
15585 c1 = ToLower(*s1++);
15586 c2 = ToLower(*s2++);
15587 if (c1 > c2) return 1;
15588 if (c1 < c2) return -1;
15589 if (c1 == NULLCHAR) return 0;
15598 return isupper(c) ? tolower(c) : c;
15606 return islower(c) ? toupper(c) : c;
15608 #endif /* !_amigados */
15616 if ((ret = (char *) malloc(strlen(s) + 1)))
15618 safeStrCpy(ret, s, strlen(s)+1);
15624 StrSavePtr(s, savePtr)
15625 char *s, **savePtr;
15630 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15631 safeStrCpy(*savePtr, s, strlen(s)+1);
15643 clock = time((time_t *)NULL);
15644 tm = localtime(&clock);
15645 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15646 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15647 return StrSave(buf);
15652 PositionToFEN(move, overrideCastling)
15654 char *overrideCastling;
15656 int i, j, fromX, fromY, toX, toY;
15663 whiteToPlay = (gameMode == EditPosition) ?
15664 !blackPlaysFirst : (move % 2 == 0);
15667 /* Piece placement data */
15668 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15670 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15671 if (boards[move][i][j] == EmptySquare) {
15673 } else { ChessSquare piece = boards[move][i][j];
15674 if (emptycount > 0) {
15675 if(emptycount<10) /* [HGM] can be >= 10 */
15676 *p++ = '0' + emptycount;
15677 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15680 if(PieceToChar(piece) == '+') {
15681 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15683 piece = (ChessSquare)(DEMOTED piece);
15685 *p++ = PieceToChar(piece);
15687 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15688 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15693 if (emptycount > 0) {
15694 if(emptycount<10) /* [HGM] can be >= 10 */
15695 *p++ = '0' + emptycount;
15696 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15703 /* [HGM] print Crazyhouse or Shogi holdings */
15704 if( gameInfo.holdingsWidth ) {
15705 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15707 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15708 piece = boards[move][i][BOARD_WIDTH-1];
15709 if( piece != EmptySquare )
15710 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15711 *p++ = PieceToChar(piece);
15713 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15714 piece = boards[move][BOARD_HEIGHT-i-1][0];
15715 if( piece != EmptySquare )
15716 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15717 *p++ = PieceToChar(piece);
15720 if( q == p ) *p++ = '-';
15726 *p++ = whiteToPlay ? 'w' : 'b';
15729 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15730 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15732 if(nrCastlingRights) {
15734 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15735 /* [HGM] write directly from rights */
15736 if(boards[move][CASTLING][2] != NoRights &&
15737 boards[move][CASTLING][0] != NoRights )
15738 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15739 if(boards[move][CASTLING][2] != NoRights &&
15740 boards[move][CASTLING][1] != NoRights )
15741 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15742 if(boards[move][CASTLING][5] != NoRights &&
15743 boards[move][CASTLING][3] != NoRights )
15744 *p++ = boards[move][CASTLING][3] + AAA;
15745 if(boards[move][CASTLING][5] != NoRights &&
15746 boards[move][CASTLING][4] != NoRights )
15747 *p++ = boards[move][CASTLING][4] + AAA;
15750 /* [HGM] write true castling rights */
15751 if( nrCastlingRights == 6 ) {
15752 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15753 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15754 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15755 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15756 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15757 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15758 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15759 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15762 if (q == p) *p++ = '-'; /* No castling rights */
15766 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15767 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15768 /* En passant target square */
15769 if (move > backwardMostMove) {
15770 fromX = moveList[move - 1][0] - AAA;
15771 fromY = moveList[move - 1][1] - ONE;
15772 toX = moveList[move - 1][2] - AAA;
15773 toY = moveList[move - 1][3] - ONE;
15774 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15775 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15776 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15778 /* 2-square pawn move just happened */
15780 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15784 } else if(move == backwardMostMove) {
15785 // [HGM] perhaps we should always do it like this, and forget the above?
15786 if((signed char)boards[move][EP_STATUS] >= 0) {
15787 *p++ = boards[move][EP_STATUS] + AAA;
15788 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15799 /* [HGM] find reversible plies */
15800 { int i = 0, j=move;
15802 if (appData.debugMode) { int k;
15803 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15804 for(k=backwardMostMove; k<=forwardMostMove; k++)
15805 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15809 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15810 if( j == backwardMostMove ) i += initialRulePlies;
15811 sprintf(p, "%d ", i);
15812 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15814 /* Fullmove number */
15815 sprintf(p, "%d", (move / 2) + 1);
15817 return StrSave(buf);
15821 ParseFEN(board, blackPlaysFirst, fen)
15823 int *blackPlaysFirst;
15833 /* [HGM] by default clear Crazyhouse holdings, if present */
15834 if(gameInfo.holdingsWidth) {
15835 for(i=0; i<BOARD_HEIGHT; i++) {
15836 board[i][0] = EmptySquare; /* black holdings */
15837 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15838 board[i][1] = (ChessSquare) 0; /* black counts */
15839 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15843 /* Piece placement data */
15844 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15847 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15848 if (*p == '/') p++;
15849 emptycount = gameInfo.boardWidth - j;
15850 while (emptycount--)
15851 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15853 #if(BOARD_FILES >= 10)
15854 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15855 p++; emptycount=10;
15856 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15857 while (emptycount--)
15858 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15860 } else if (isdigit(*p)) {
15861 emptycount = *p++ - '0';
15862 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15863 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15864 while (emptycount--)
15865 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15866 } else if (*p == '+' || isalpha(*p)) {
15867 if (j >= gameInfo.boardWidth) return FALSE;
15869 piece = CharToPiece(*++p);
15870 if(piece == EmptySquare) return FALSE; /* unknown piece */
15871 piece = (ChessSquare) (PROMOTED piece ); p++;
15872 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15873 } else piece = CharToPiece(*p++);
15875 if(piece==EmptySquare) return FALSE; /* unknown piece */
15876 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15877 piece = (ChessSquare) (PROMOTED piece);
15878 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15881 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15887 while (*p == '/' || *p == ' ') p++;
15889 /* [HGM] look for Crazyhouse holdings here */
15890 while(*p==' ') p++;
15891 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15893 if(*p == '-' ) p++; /* empty holdings */ else {
15894 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15895 /* if we would allow FEN reading to set board size, we would */
15896 /* have to add holdings and shift the board read so far here */
15897 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15899 if((int) piece >= (int) BlackPawn ) {
15900 i = (int)piece - (int)BlackPawn;
15901 i = PieceToNumber((ChessSquare)i);
15902 if( i >= gameInfo.holdingsSize ) return FALSE;
15903 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15904 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15906 i = (int)piece - (int)WhitePawn;
15907 i = PieceToNumber((ChessSquare)i);
15908 if( i >= gameInfo.holdingsSize ) return FALSE;
15909 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15910 board[i][BOARD_WIDTH-2]++; /* black holdings */
15917 while(*p == ' ') p++;
15921 if(appData.colorNickNames) {
15922 if( c == appData.colorNickNames[0] ) c = 'w'; else
15923 if( c == appData.colorNickNames[1] ) c = 'b';
15927 *blackPlaysFirst = FALSE;
15930 *blackPlaysFirst = TRUE;
15936 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15937 /* return the extra info in global variiables */
15939 /* set defaults in case FEN is incomplete */
15940 board[EP_STATUS] = EP_UNKNOWN;
15941 for(i=0; i<nrCastlingRights; i++ ) {
15942 board[CASTLING][i] =
15943 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15944 } /* assume possible unless obviously impossible */
15945 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15946 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15947 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15948 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15949 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15950 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15951 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15952 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15955 while(*p==' ') p++;
15956 if(nrCastlingRights) {
15957 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15958 /* castling indicator present, so default becomes no castlings */
15959 for(i=0; i<nrCastlingRights; i++ ) {
15960 board[CASTLING][i] = NoRights;
15963 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15964 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15965 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15966 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15967 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15969 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15970 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15971 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15973 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15974 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15975 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15976 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15977 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15978 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15981 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15982 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15983 board[CASTLING][2] = whiteKingFile;
15986 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15987 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15988 board[CASTLING][2] = whiteKingFile;
15991 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15992 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15993 board[CASTLING][5] = blackKingFile;
15996 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15997 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15998 board[CASTLING][5] = blackKingFile;
16001 default: /* FRC castlings */
16002 if(c >= 'a') { /* black rights */
16003 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16004 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16005 if(i == BOARD_RGHT) break;
16006 board[CASTLING][5] = i;
16008 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16009 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16011 board[CASTLING][3] = c;
16013 board[CASTLING][4] = c;
16014 } else { /* white rights */
16015 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16016 if(board[0][i] == WhiteKing) break;
16017 if(i == BOARD_RGHT) break;
16018 board[CASTLING][2] = i;
16019 c -= AAA - 'a' + 'A';
16020 if(board[0][c] >= WhiteKing) break;
16022 board[CASTLING][0] = c;
16024 board[CASTLING][1] = c;
16028 for(i=0; i<nrCastlingRights; i++)
16029 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16030 if (appData.debugMode) {
16031 fprintf(debugFP, "FEN castling rights:");
16032 for(i=0; i<nrCastlingRights; i++)
16033 fprintf(debugFP, " %d", board[CASTLING][i]);
16034 fprintf(debugFP, "\n");
16037 while(*p==' ') p++;
16040 /* read e.p. field in games that know e.p. capture */
16041 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16042 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16044 p++; board[EP_STATUS] = EP_NONE;
16046 char c = *p++ - AAA;
16048 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16049 if(*p >= '0' && *p <='9') p++;
16050 board[EP_STATUS] = c;
16055 if(sscanf(p, "%d", &i) == 1) {
16056 FENrulePlies = i; /* 50-move ply counter */
16057 /* (The move number is still ignored) */
16064 EditPositionPasteFEN(char *fen)
16067 Board initial_position;
16069 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16070 DisplayError(_("Bad FEN position in clipboard"), 0);
16073 int savedBlackPlaysFirst = blackPlaysFirst;
16074 EditPositionEvent();
16075 blackPlaysFirst = savedBlackPlaysFirst;
16076 CopyBoard(boards[0], initial_position);
16077 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16078 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16079 DisplayBothClocks();
16080 DrawPosition(FALSE, boards[currentMove]);
16085 static char cseq[12] = "\\ ";
16087 Boolean set_cont_sequence(char *new_seq)
16092 // handle bad attempts to set the sequence
16094 return 0; // acceptable error - no debug
16096 len = strlen(new_seq);
16097 ret = (len > 0) && (len < sizeof(cseq));
16099 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16100 else if (appData.debugMode)
16101 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16106 reformat a source message so words don't cross the width boundary. internal
16107 newlines are not removed. returns the wrapped size (no null character unless
16108 included in source message). If dest is NULL, only calculate the size required
16109 for the dest buffer. lp argument indicats line position upon entry, and it's
16110 passed back upon exit.
16112 int wrap(char *dest, char *src, int count, int width, int *lp)
16114 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16116 cseq_len = strlen(cseq);
16117 old_line = line = *lp;
16118 ansi = len = clen = 0;
16120 for (i=0; i < count; i++)
16122 if (src[i] == '\033')
16125 // if we hit the width, back up
16126 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16128 // store i & len in case the word is too long
16129 old_i = i, old_len = len;
16131 // find the end of the last word
16132 while (i && src[i] != ' ' && src[i] != '\n')
16138 // word too long? restore i & len before splitting it
16139 if ((old_i-i+clen) >= width)
16146 if (i && src[i-1] == ' ')
16149 if (src[i] != ' ' && src[i] != '\n')
16156 // now append the newline and continuation sequence
16161 strncpy(dest+len, cseq, cseq_len);
16169 dest[len] = src[i];
16173 if (src[i] == '\n')
16178 if (dest && appData.debugMode)
16180 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16181 count, width, line, len, *lp);
16182 show_bytes(debugFP, src, count);
16183 fprintf(debugFP, "\ndest: ");
16184 show_bytes(debugFP, dest, len);
16185 fprintf(debugFP, "\n");
16187 *lp = dest ? line : old_line;
16192 // [HGM] vari: routines for shelving variations
16195 PushInner(int firstMove, int lastMove)
16197 int i, j, nrMoves = lastMove - firstMove;
16199 // push current tail of game on stack
16200 savedResult[storedGames] = gameInfo.result;
16201 savedDetails[storedGames] = gameInfo.resultDetails;
16202 gameInfo.resultDetails = NULL;
16203 savedFirst[storedGames] = firstMove;
16204 savedLast [storedGames] = lastMove;
16205 savedFramePtr[storedGames] = framePtr;
16206 framePtr -= nrMoves; // reserve space for the boards
16207 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16208 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16209 for(j=0; j<MOVE_LEN; j++)
16210 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16211 for(j=0; j<2*MOVE_LEN; j++)
16212 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16213 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16214 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16215 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16216 pvInfoList[firstMove+i-1].depth = 0;
16217 commentList[framePtr+i] = commentList[firstMove+i];
16218 commentList[firstMove+i] = NULL;
16222 forwardMostMove = firstMove; // truncate game so we can start variation
16226 PushTail(int firstMove, int lastMove)
16228 if(appData.icsActive) { // only in local mode
16229 forwardMostMove = currentMove; // mimic old ICS behavior
16232 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16234 PushInner(firstMove, lastMove);
16235 if(storedGames == 1) GreyRevert(FALSE);
16239 PopInner(Boolean annotate)
16242 char buf[8000], moveBuf[20];
16245 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16246 nrMoves = savedLast[storedGames] - currentMove;
16249 if(!WhiteOnMove(currentMove))
16250 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16251 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16252 for(i=currentMove; i<forwardMostMove; i++) {
16254 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16255 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16256 strcat(buf, moveBuf);
16257 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16258 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16262 for(i=1; i<=nrMoves; i++) { // copy last variation back
16263 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16264 for(j=0; j<MOVE_LEN; j++)
16265 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16266 for(j=0; j<2*MOVE_LEN; j++)
16267 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16268 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16269 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16270 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16271 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16272 commentList[currentMove+i] = commentList[framePtr+i];
16273 commentList[framePtr+i] = NULL;
16275 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16276 framePtr = savedFramePtr[storedGames];
16277 gameInfo.result = savedResult[storedGames];
16278 if(gameInfo.resultDetails != NULL) {
16279 free(gameInfo.resultDetails);
16281 gameInfo.resultDetails = savedDetails[storedGames];
16282 forwardMostMove = currentMove + nrMoves;
16286 PopTail(Boolean annotate)
16288 if(appData.icsActive) return FALSE; // only in local mode
16289 if(!storedGames) return FALSE; // sanity
16290 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16292 PopInner(annotate);
16294 if(storedGames == 0) GreyRevert(TRUE);
16300 { // remove all shelved variations
16302 for(i=0; i<storedGames; i++) {
16303 if(savedDetails[i])
16304 free(savedDetails[i]);
16305 savedDetails[i] = NULL;
16307 for(i=framePtr; i<MAX_MOVES; i++) {
16308 if(commentList[i]) free(commentList[i]);
16309 commentList[i] = NULL;
16311 framePtr = MAX_MOVES-1;
16316 LoadVariation(int index, char *text)
16317 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16318 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16319 int level = 0, move;
16321 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16322 // first find outermost bracketing variation
16323 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16324 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16325 if(*p == '{') wait = '}'; else
16326 if(*p == '[') wait = ']'; else
16327 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16328 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16330 if(*p == wait) wait = NULLCHAR; // closing ]} found
16333 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16334 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16335 end[1] = NULLCHAR; // clip off comment beyond variation
16336 ToNrEvent(currentMove-1);
16337 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16338 // kludge: use ParsePV() to append variation to game
16339 move = currentMove;
16340 ParsePV(start, TRUE, TRUE);
16341 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16342 ClearPremoveHighlights();
16344 ToNrEvent(currentMove+1);