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 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176 /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188 char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190 int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 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, pairing;
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)
496 Boolean shuffleOpenings;
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 }
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
616 #define GothicArray CapablancaArray
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
622 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
623 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
624 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
627 #define FalconArray CapablancaArray
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
649 Board initialPosition;
652 /* Convert str to a rating. Checks for special cases of "----",
654 "++++", etc. Also strips ()'s */
656 string_to_rating(str)
659 while(*str && !isdigit(*str)) ++str;
661 return 0; /* One of the special "no rating" cases */
669 /* Init programStats */
670 programStats.movelist[0] = 0;
671 programStats.depth = 0;
672 programStats.nr_moves = 0;
673 programStats.moves_left = 0;
674 programStats.nodes = 0;
675 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
676 programStats.score = 0;
677 programStats.got_only_move = 0;
678 programStats.got_fail = 0;
679 programStats.line_is_book = 0;
684 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685 if (appData.firstPlaysBlack) {
686 first.twoMachinesColor = "black\n";
687 second.twoMachinesColor = "white\n";
689 first.twoMachinesColor = "white\n";
690 second.twoMachinesColor = "black\n";
693 first.other = &second;
694 second.other = &first;
697 if(appData.timeOddsMode) {
698 norm = appData.timeOdds[0];
699 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
701 first.timeOdds = appData.timeOdds[0]/norm;
702 second.timeOdds = appData.timeOdds[1]/norm;
705 if(programVersion) free(programVersion);
706 if (appData.noChessProgram) {
707 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708 sprintf(programVersion, "%s", PACKAGE_STRING);
710 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
717 UnloadEngine(ChessProgramState *cps)
719 /* Kill off first chess program */
720 if (cps->isr != NULL)
721 RemoveInputSource(cps->isr);
724 if (cps->pr != NoProc) {
726 DoSleep( appData.delayBeforeQuit );
727 SendToProgram("quit\n", cps);
728 DoSleep( appData.delayAfterQuit );
729 DestroyChildProcess(cps->pr, cps->useSigterm);
732 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 ClearOptions(ChessProgramState *cps)
739 cps->nrOptions = cps->comboCnt = 0;
740 for(i=0; i<MAX_OPTIONS; i++) {
741 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742 cps->option[i].textValue = 0;
746 char *engineNames[] = {
752 InitEngine(ChessProgramState *cps, int n)
753 { // [HGM] all engine initialiation put in a function that does one engine
757 cps->which = engineNames[n];
758 cps->maybeThinking = FALSE;
762 cps->sendDrawOffers = 1;
764 cps->program = appData.chessProgram[n];
765 cps->host = appData.host[n];
766 cps->dir = appData.directory[n];
767 cps->initString = appData.engInitString[n];
768 cps->computerString = appData.computerString[n];
769 cps->useSigint = TRUE;
770 cps->useSigterm = TRUE;
771 cps->reuse = appData.reuse[n];
772 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
773 cps->useSetboard = FALSE;
775 cps->usePing = FALSE;
778 cps->usePlayother = FALSE;
779 cps->useColors = TRUE;
780 cps->useUsermove = FALSE;
781 cps->sendICS = FALSE;
782 cps->sendName = appData.icsActive;
783 cps->sdKludge = FALSE;
784 cps->stKludge = FALSE;
785 TidyProgramName(cps->program, cps->host, cps->tidy);
787 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788 cps->analysisSupport = 2; /* detect */
789 cps->analyzing = FALSE;
790 cps->initDone = FALSE;
792 /* New features added by Tord: */
793 cps->useFEN960 = FALSE;
794 cps->useOOCastle = TRUE;
795 /* End of new features added by Tord. */
796 cps->fenOverride = appData.fenOverride[n];
798 /* [HGM] time odds: set factor for each machine */
799 cps->timeOdds = appData.timeOdds[n];
801 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802 cps->accumulateTC = appData.accumulateTC[n];
803 cps->maxNrOfSessions = 1;
808 cps->supportsNPS = UNKNOWN;
809 cps->memSize = FALSE;
810 cps->maxCores = FALSE;
811 cps->egtFormats[0] = NULLCHAR;
814 cps->optionSettings = appData.engOptions[n];
816 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817 cps->isUCI = appData.isUCI[n]; /* [AS] */
818 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
820 if (appData.protocolVersion[n] > PROTOVER
821 || appData.protocolVersion[n] < 1)
826 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827 appData.protocolVersion[n]);
828 if( (len > MSG_SIZ) && appData.debugMode )
829 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
831 DisplayFatalError(buf, 0, 2);
835 cps->protocolVersion = appData.protocolVersion[n];
838 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
841 ChessProgramState *savCps;
847 if(WaitForEngine(savCps, LoadEngine)) return;
848 CommonEngineInit(); // recalculate time odds
849 if(gameInfo.variant != StringToVariant(appData.variant)) {
850 // we changed variant when loading the engine; this forces us to reset
851 Reset(TRUE, savCps != &first);
852 EditGameEvent(); // for consistency with other path, as Reset changes mode
854 InitChessProgram(savCps, FALSE);
855 SendToProgram("force\n", savCps);
856 DisplayMessage("", "");
857 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
864 ReplaceEngine(ChessProgramState *cps, int n)
868 appData.noChessProgram = FALSE;
869 appData.clockMode = TRUE;
872 if(n) return; // only startup first engine immediately; second can wait
873 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
880 static char resetOptions[] =
881 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
885 Load(ChessProgramState *cps, int i)
887 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
892 ParseArgsFromString(buf);
894 ReplaceEngine(cps, i);
898 while(q = strchr(p, SLASH)) p = q+1;
899 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900 if(engineDir[0] != NULLCHAR)
901 appData.directory[i] = engineDir;
902 else if(p != engineName) { // derive directory from engine path, when not given
904 appData.directory[i] = strdup(engineName);
906 } else appData.directory[i] = ".";
908 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909 snprintf(command, MSG_SIZ, "%s %s", p, params);
912 appData.chessProgram[i] = strdup(p);
913 appData.isUCI[i] = isUCI;
914 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915 appData.hasOwnBookUCI[i] = hasBook;
916 if(!nickName[0]) useNick = FALSE;
917 if(useNick) ASSIGN(appData.pgnName[i], nickName);
921 q = firstChessProgramNames;
922 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925 quote, p, quote, appData.directory[i],
926 useNick ? " -fn \"" : "",
927 useNick ? nickName : "",
929 v1 ? " -firstProtocolVersion 1" : "",
930 hasBook ? "" : " -fNoOwnBookUCI",
931 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932 storeVariant ? " -variant " : "",
933 storeVariant ? VariantName(gameInfo.variant) : "");
934 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
938 ReplaceEngine(cps, i);
944 int matched, min, sec;
946 * Parse timeControl resource
948 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949 appData.movesPerSession)) {
951 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952 DisplayFatalError(buf, 0, 2);
956 * Parse searchTime resource
958 if (*appData.searchTime != NULLCHAR) {
959 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
961 searchTime = min * 60;
962 } else if (matched == 2) {
963 searchTime = min * 60 + sec;
966 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967 DisplayFatalError(buf, 0, 2);
976 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
979 GetTimeMark(&programStartTime);
980 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981 appData.seedBase = random() + (random()<<15);
982 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
985 programStats.ok_to_send = 1;
986 programStats.seen_stat = 0;
989 * Initialize game list
995 * Internet chess server status
997 if (appData.icsActive) {
998 appData.matchMode = FALSE;
999 appData.matchGames = 0;
1001 appData.noChessProgram = !appData.zippyPlay;
1003 appData.zippyPlay = FALSE;
1004 appData.zippyTalk = FALSE;
1005 appData.noChessProgram = TRUE;
1007 if (*appData.icsHelper != NULLCHAR) {
1008 appData.useTelnet = TRUE;
1009 appData.telnetProgram = appData.icsHelper;
1012 appData.zippyTalk = appData.zippyPlay = FALSE;
1015 /* [AS] Initialize pv info list [HGM] and game state */
1019 for( i=0; i<=framePtr; i++ ) {
1020 pvInfoList[i].depth = -1;
1021 boards[i][EP_STATUS] = EP_NONE;
1022 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1028 /* [AS] Adjudication threshold */
1029 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031 InitEngine(&first, 0);
1032 InitEngine(&second, 1);
1035 pairing.which = "pairing"; // pairing engine
1036 pairing.pr = NoProc;
1038 pairing.program = appData.pairingEngine;
1039 pairing.host = "localhost";
1042 if (appData.icsActive) {
1043 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1044 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045 appData.clockMode = FALSE;
1046 first.sendTime = second.sendTime = 0;
1050 /* Override some settings from environment variables, for backward
1051 compatibility. Unfortunately it's not feasible to have the env
1052 vars just set defaults, at least in xboard. Ugh.
1054 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1059 if (!appData.icsActive) {
1063 /* Check for variants that are supported only in ICS mode,
1064 or not at all. Some that are accepted here nevertheless
1065 have bugs; see comments below.
1067 VariantClass variant = StringToVariant(appData.variant);
1069 case VariantBughouse: /* need four players and two boards */
1070 case VariantKriegspiel: /* need to hide pieces and move details */
1071 /* case VariantFischeRandom: (Fabien: moved below) */
1072 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073 if( (len > MSG_SIZ) && appData.debugMode )
1074 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076 DisplayFatalError(buf, 0, 2);
1079 case VariantUnknown:
1080 case VariantLoadable:
1090 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091 if( (len > MSG_SIZ) && appData.debugMode )
1092 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094 DisplayFatalError(buf, 0, 2);
1097 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1098 case VariantFairy: /* [HGM] TestLegality definitely off! */
1099 case VariantGothic: /* [HGM] should work */
1100 case VariantCapablanca: /* [HGM] should work */
1101 case VariantCourier: /* [HGM] initial forced moves not implemented */
1102 case VariantShogi: /* [HGM] could still mate with pawn drop */
1103 case VariantKnightmate: /* [HGM] should work */
1104 case VariantCylinder: /* [HGM] untested */
1105 case VariantFalcon: /* [HGM] untested */
1106 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107 offboard interposition not understood */
1108 case VariantNormal: /* definitely works! */
1109 case VariantWildCastle: /* pieces not automatically shuffled */
1110 case VariantNoCastle: /* pieces not automatically shuffled */
1111 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112 case VariantLosers: /* should work except for win condition,
1113 and doesn't know captures are mandatory */
1114 case VariantSuicide: /* should work except for win condition,
1115 and doesn't know captures are mandatory */
1116 case VariantGiveaway: /* should work except for win condition,
1117 and doesn't know captures are mandatory */
1118 case VariantTwoKings: /* should work */
1119 case VariantAtomic: /* should work except for win condition */
1120 case Variant3Check: /* should work except for win condition */
1121 case VariantShatranj: /* should work except for all win conditions */
1122 case VariantMakruk: /* should work except for draw countdown */
1123 case VariantBerolina: /* might work if TestLegality is off */
1124 case VariantCapaRandom: /* should work */
1125 case VariantJanus: /* should work */
1126 case VariantSuper: /* experimental */
1127 case VariantGreat: /* experimental, requires legality testing to be off */
1128 case VariantSChess: /* S-Chess, should work */
1129 case VariantGrand: /* should work */
1130 case VariantSpartan: /* should work */
1137 int NextIntegerFromString( char ** str, long * value )
1142 while( *s == ' ' || *s == '\t' ) {
1148 if( *s >= '0' && *s <= '9' ) {
1149 while( *s >= '0' && *s <= '9' ) {
1150 *value = *value * 10 + (*s - '0');
1162 int NextTimeControlFromString( char ** str, long * value )
1165 int result = NextIntegerFromString( str, &temp );
1168 *value = temp * 60; /* Minutes */
1169 if( **str == ':' ) {
1171 result = NextIntegerFromString( str, &temp );
1172 *value += temp; /* Seconds */
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181 int result = -1, type = 0; long temp, temp2;
1183 if(**str != ':') return -1; // old params remain in force!
1185 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186 if( NextIntegerFromString( str, &temp ) ) return -1;
1187 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1190 /* time only: incremental or sudden-death time control */
1191 if(**str == '+') { /* increment follows; read it */
1193 if(**str == '!') type = *(*str)++; // Bronstein TC
1194 if(result = NextIntegerFromString( str, &temp2)) return -1;
1195 *inc = temp2 * 1000;
1196 if(**str == '.') { // read fraction of increment
1197 char *start = ++(*str);
1198 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200 while(start++ < *str) temp2 /= 10;
1204 *moves = 0; *tc = temp * 1000; *incType = type;
1208 (*str)++; /* classical time control */
1209 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 { /* [HGM] get time to add from the multi-session time-control string */
1222 int incType, moves=1; /* kludge to force reading of first session */
1223 long time, increment;
1226 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232 if(movenr == -1) return time; /* last move before new session */
1233 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235 if(!moves) return increment; /* current session is incremental */
1236 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237 } while(movenr >= -1); /* try again for next session */
1239 return 0; // no new time quota on this move
1243 ParseTimeControl(tc, ti, mps)
1250 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1253 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1259 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1264 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266 snprintf(buf, MSG_SIZ, ":%s", mytc);
1268 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1275 /* Parse second time control */
1278 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1286 timeControl_2 = tc2 * 1000;
1296 timeControl = tc1 * 1000;
1299 timeIncrement = ti * 1000; /* convert to ms */
1300 movesPerSession = 0;
1303 movesPerSession = mps;
1311 if (appData.debugMode) {
1312 fprintf(debugFP, "%s\n", programVersion);
1315 set_cont_sequence(appData.wrapContSeq);
1316 if (appData.matchGames > 0) {
1317 appData.matchMode = TRUE;
1318 } else if (appData.matchMode) {
1319 appData.matchGames = 1;
1321 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322 appData.matchGames = appData.sameColorGames;
1323 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1328 if (appData.noChessProgram || first.protocolVersion == 1) {
1331 /* kludge: allow timeout for initial "feature" commands */
1333 DisplayMessage("", _("Starting chess program"));
1334 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1339 CalculateIndex(int index, int gameNr)
1340 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342 if(index > 0) return index; // fixed nmber
1343 if(index == 0) return 1;
1344 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1350 LoadGameOrPosition(int gameNr)
1351 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352 if (*appData.loadGameFile != NULLCHAR) {
1353 if (!LoadGameFromFile(appData.loadGameFile,
1354 CalculateIndex(appData.loadGameIndex, gameNr),
1355 appData.loadGameFile, FALSE)) {
1356 DisplayFatalError(_("Bad game file"), 0, 1);
1359 } else if (*appData.loadPositionFile != NULLCHAR) {
1360 if (!LoadPositionFromFile(appData.loadPositionFile,
1361 CalculateIndex(appData.loadPositionIndex, gameNr),
1362 appData.loadPositionFile)) {
1363 DisplayFatalError(_("Bad position file"), 0, 1);
1371 ReserveGame(int gameNr, char resChar)
1373 FILE *tf = fopen(appData.tourneyFile, "r+");
1374 char *p, *q, c, buf[MSG_SIZ];
1375 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376 safeStrCpy(buf, lastMsg, MSG_SIZ);
1377 DisplayMessage(_("Pick new game"), "");
1378 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379 ParseArgsFromFile(tf);
1380 p = q = appData.results;
1381 if(appData.debugMode) {
1382 char *r = appData.participants;
1383 fprintf(debugFP, "results = '%s'\n", p);
1384 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385 fprintf(debugFP, "\n");
1387 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390 safeStrCpy(q, p, strlen(p) + 2);
1391 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1397 fseek(tf, -(strlen(p)+4), SEEK_END);
1399 if(c != '"') // depending on DOS or Unix line endings we can be one off
1400 fseek(tf, -(strlen(p)+2), SEEK_END);
1401 else fseek(tf, -(strlen(p)+3), SEEK_END);
1402 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403 DisplayMessage(buf, "");
1404 free(p); appData.results = q;
1405 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407 UnloadEngine(&first); // next game belongs to other pairing;
1408 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1413 MatchEvent(int mode)
1414 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416 if(matchMode) { // already in match mode: switch it off
1418 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1421 // if(gameMode != BeginningOfGame) {
1422 // DisplayError(_("You can only start a match from the initial position."), 0);
1426 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427 /* Set up machine vs. machine match */
1429 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430 if(appData.tourneyFile[0]) {
1432 if(nextGame > appData.matchGames) {
1434 if(strchr(appData.results, '*') == NULL) {
1436 appData.tourneyCycles++;
1437 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1439 NextTourneyGame(-1, &dummy);
1441 if(nextGame <= appData.matchGames) {
1442 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444 ScheduleDelayedEvent(NextMatchGame, 10000);
1449 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450 DisplayError(buf, 0);
1451 appData.tourneyFile[0] = 0;
1455 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1456 DisplayFatalError(_("Can't have a match with no chess programs"),
1461 matchGame = roundNr = 1;
1462 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1467 InitBackEnd3 P((void))
1469 GameMode initialMode;
1473 InitChessProgram(&first, startedFromSetupPosition);
1475 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1476 free(programVersion);
1477 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1481 if (appData.icsActive) {
1483 /* [DM] Make a console window if needed [HGM] merged ifs */
1489 if (*appData.icsCommPort != NULLCHAR)
1490 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491 appData.icsCommPort);
1493 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494 appData.icsHost, appData.icsPort);
1496 if( (len > MSG_SIZ) && appData.debugMode )
1497 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499 DisplayFatalError(buf, err, 1);
1504 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509 } else if (appData.noChessProgram) {
1515 if (*appData.cmailGameName != NULLCHAR) {
1517 OpenLoopback(&cmailPR);
1519 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1523 DisplayMessage("", "");
1524 if (StrCaseCmp(appData.initialMode, "") == 0) {
1525 initialMode = BeginningOfGame;
1526 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1532 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533 initialMode = TwoMachinesPlay;
1534 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535 initialMode = AnalyzeFile;
1536 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537 initialMode = AnalyzeMode;
1538 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539 initialMode = MachinePlaysWhite;
1540 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541 initialMode = MachinePlaysBlack;
1542 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543 initialMode = EditGame;
1544 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545 initialMode = EditPosition;
1546 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547 initialMode = Training;
1549 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550 if( (len > MSG_SIZ) && appData.debugMode )
1551 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553 DisplayFatalError(buf, 0, 2);
1557 if (appData.matchMode) {
1558 if(appData.tourneyFile[0]) { // start tourney from command line
1560 if(f = fopen(appData.tourneyFile, "r")) {
1561 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563 appData.clockMode = TRUE;
1565 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1568 } else if (*appData.cmailGameName != NULLCHAR) {
1569 /* Set up cmail mode */
1570 ReloadCmailMsgEvent(TRUE);
1572 /* Set up other modes */
1573 if (initialMode == AnalyzeFile) {
1574 if (*appData.loadGameFile == NULLCHAR) {
1575 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1579 if (*appData.loadGameFile != NULLCHAR) {
1580 (void) LoadGameFromFile(appData.loadGameFile,
1581 appData.loadGameIndex,
1582 appData.loadGameFile, TRUE);
1583 } else if (*appData.loadPositionFile != NULLCHAR) {
1584 (void) LoadPositionFromFile(appData.loadPositionFile,
1585 appData.loadPositionIndex,
1586 appData.loadPositionFile);
1587 /* [HGM] try to make self-starting even after FEN load */
1588 /* to allow automatic setup of fairy variants with wtm */
1589 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590 gameMode = BeginningOfGame;
1591 setboardSpoiledMachineBlack = 1;
1593 /* [HGM] loadPos: make that every new game uses the setup */
1594 /* from file as long as we do not switch variant */
1595 if(!blackPlaysFirst) {
1596 startedFromPositionFile = TRUE;
1597 CopyBoard(filePosition, boards[0]);
1600 if (initialMode == AnalyzeMode) {
1601 if (appData.noChessProgram) {
1602 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1605 if (appData.icsActive) {
1606 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1610 } else if (initialMode == AnalyzeFile) {
1611 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612 ShowThinkingEvent();
1614 AnalysisPeriodicEvent(1);
1615 } else if (initialMode == MachinePlaysWhite) {
1616 if (appData.noChessProgram) {
1617 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1621 if (appData.icsActive) {
1622 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1626 MachineWhiteEvent();
1627 } else if (initialMode == MachinePlaysBlack) {
1628 if (appData.noChessProgram) {
1629 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1633 if (appData.icsActive) {
1634 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1638 MachineBlackEvent();
1639 } else if (initialMode == TwoMachinesPlay) {
1640 if (appData.noChessProgram) {
1641 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1645 if (appData.icsActive) {
1646 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1651 } else if (initialMode == EditGame) {
1653 } else if (initialMode == EditPosition) {
1654 EditPositionEvent();
1655 } else if (initialMode == Training) {
1656 if (*appData.loadGameFile == NULLCHAR) {
1657 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1666 * Establish will establish a contact to a remote host.port.
1667 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1668 * used to talk to the host.
1669 * Returns 0 if okay, error code if not.
1676 if (*appData.icsCommPort != NULLCHAR) {
1677 /* Talk to the host through a serial comm port */
1678 return OpenCommPort(appData.icsCommPort, &icsPR);
1680 } else if (*appData.gateway != NULLCHAR) {
1681 if (*appData.remoteShell == NULLCHAR) {
1682 /* Use the rcmd protocol to run telnet program on a gateway host */
1683 snprintf(buf, sizeof(buf), "%s %s %s",
1684 appData.telnetProgram, appData.icsHost, appData.icsPort);
1685 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1688 /* Use the rsh program to run telnet program on a gateway host */
1689 if (*appData.remoteUser == NULLCHAR) {
1690 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1691 appData.gateway, appData.telnetProgram,
1692 appData.icsHost, appData.icsPort);
1694 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1695 appData.remoteShell, appData.gateway,
1696 appData.remoteUser, appData.telnetProgram,
1697 appData.icsHost, appData.icsPort);
1699 return StartChildProcess(buf, "", &icsPR);
1702 } else if (appData.useTelnet) {
1703 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1706 /* TCP socket interface differs somewhat between
1707 Unix and NT; handle details in the front end.
1709 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1713 void EscapeExpand(char *p, char *q)
1714 { // [HGM] initstring: routine to shape up string arguments
1715 while(*p++ = *q++) if(p[-1] == '\\')
1717 case 'n': p[-1] = '\n'; break;
1718 case 'r': p[-1] = '\r'; break;
1719 case 't': p[-1] = '\t'; break;
1720 case '\\': p[-1] = '\\'; break;
1721 case 0: *p = 0; return;
1722 default: p[-1] = q[-1]; break;
1727 show_bytes(fp, buf, count)
1733 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1734 fprintf(fp, "\\%03o", *buf & 0xff);
1743 /* Returns an errno value */
1745 OutputMaybeTelnet(pr, message, count, outError)
1751 char buf[8192], *p, *q, *buflim;
1752 int left, newcount, outcount;
1754 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1755 *appData.gateway != NULLCHAR) {
1756 if (appData.debugMode) {
1757 fprintf(debugFP, ">ICS: ");
1758 show_bytes(debugFP, message, count);
1759 fprintf(debugFP, "\n");
1761 return OutputToProcess(pr, message, count, outError);
1764 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1771 if (appData.debugMode) {
1772 fprintf(debugFP, ">ICS: ");
1773 show_bytes(debugFP, buf, newcount);
1774 fprintf(debugFP, "\n");
1776 outcount = OutputToProcess(pr, buf, newcount, outError);
1777 if (outcount < newcount) return -1; /* to be sure */
1784 } else if (((unsigned char) *p) == TN_IAC) {
1785 *q++ = (char) TN_IAC;
1792 if (appData.debugMode) {
1793 fprintf(debugFP, ">ICS: ");
1794 show_bytes(debugFP, buf, newcount);
1795 fprintf(debugFP, "\n");
1797 outcount = OutputToProcess(pr, buf, newcount, outError);
1798 if (outcount < newcount) return -1; /* to be sure */
1803 read_from_player(isr, closure, message, count, error)
1810 int outError, outCount;
1811 static int gotEof = 0;
1813 /* Pass data read from player on to ICS */
1816 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1817 if (outCount < count) {
1818 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820 } else if (count < 0) {
1821 RemoveInputSource(isr);
1822 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1823 } else if (gotEof++ > 0) {
1824 RemoveInputSource(isr);
1825 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1831 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1832 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1833 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1834 SendToICS("date\n");
1835 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1838 /* added routine for printf style output to ics */
1839 void ics_printf(char *format, ...)
1841 char buffer[MSG_SIZ];
1844 va_start(args, format);
1845 vsnprintf(buffer, sizeof(buffer), format, args);
1846 buffer[sizeof(buffer)-1] = '\0';
1855 int count, outCount, outError;
1857 if (icsPR == NULL) return;
1860 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1861 if (outCount < count) {
1862 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866 /* This is used for sending logon scripts to the ICS. Sending
1867 without a delay causes problems when using timestamp on ICC
1868 (at least on my machine). */
1870 SendToICSDelayed(s,msdelay)
1874 int count, outCount, outError;
1876 if (icsPR == NULL) return;
1879 if (appData.debugMode) {
1880 fprintf(debugFP, ">ICS: ");
1881 show_bytes(debugFP, s, count);
1882 fprintf(debugFP, "\n");
1884 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1886 if (outCount < count) {
1887 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892 /* Remove all highlighting escape sequences in s
1893 Also deletes any suffix starting with '('
1896 StripHighlightAndTitle(s)
1899 static char retbuf[MSG_SIZ];
1902 while (*s != NULLCHAR) {
1903 while (*s == '\033') {
1904 while (*s != NULLCHAR && !isalpha(*s)) s++;
1905 if (*s != NULLCHAR) s++;
1907 while (*s != NULLCHAR && *s != '\033') {
1908 if (*s == '(' || *s == '[') {
1919 /* Remove all highlighting escape sequences in s */
1924 static char retbuf[MSG_SIZ];
1927 while (*s != NULLCHAR) {
1928 while (*s == '\033') {
1929 while (*s != NULLCHAR && !isalpha(*s)) s++;
1930 if (*s != NULLCHAR) s++;
1932 while (*s != NULLCHAR && *s != '\033') {
1940 char *variantNames[] = VARIANT_NAMES;
1945 return variantNames[v];
1949 /* Identify a variant from the strings the chess servers use or the
1950 PGN Variant tag names we use. */
1957 VariantClass v = VariantNormal;
1958 int i, found = FALSE;
1964 /* [HGM] skip over optional board-size prefixes */
1965 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1966 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1967 while( *e++ != '_');
1970 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1974 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1975 if (StrCaseStr(e, variantNames[i])) {
1976 v = (VariantClass) i;
1983 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1984 || StrCaseStr(e, "wild/fr")
1985 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1986 v = VariantFischeRandom;
1987 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1988 (i = 1, p = StrCaseStr(e, "w"))) {
1990 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1997 case 0: /* FICS only, actually */
1999 /* Castling legal even if K starts on d-file */
2000 v = VariantWildCastle;
2005 /* Castling illegal even if K & R happen to start in
2006 normal positions. */
2007 v = VariantNoCastle;
2020 /* Castling legal iff K & R start in normal positions */
2026 /* Special wilds for position setup; unclear what to do here */
2027 v = VariantLoadable;
2030 /* Bizarre ICC game */
2031 v = VariantTwoKings;
2034 v = VariantKriegspiel;
2040 v = VariantFischeRandom;
2043 v = VariantCrazyhouse;
2046 v = VariantBughouse;
2052 /* Not quite the same as FICS suicide! */
2053 v = VariantGiveaway;
2059 v = VariantShatranj;
2062 /* Temporary names for future ICC types. The name *will* change in
2063 the next xboard/WinBoard release after ICC defines it. */
2101 v = VariantCapablanca;
2104 v = VariantKnightmate;
2110 v = VariantCylinder;
2116 v = VariantCapaRandom;
2119 v = VariantBerolina;
2131 /* Found "wild" or "w" in the string but no number;
2132 must assume it's normal chess. */
2136 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2137 if( (len > MSG_SIZ) && appData.debugMode )
2138 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2140 DisplayError(buf, 0);
2146 if (appData.debugMode) {
2147 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2148 e, wnum, VariantName(v));
2153 static int leftover_start = 0, leftover_len = 0;
2154 char star_match[STAR_MATCH_N][MSG_SIZ];
2156 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2157 advance *index beyond it, and set leftover_start to the new value of
2158 *index; else return FALSE. If pattern contains the character '*', it
2159 matches any sequence of characters not containing '\r', '\n', or the
2160 character following the '*' (if any), and the matched sequence(s) are
2161 copied into star_match.
2164 looking_at(buf, index, pattern)
2169 char *bufp = &buf[*index], *patternp = pattern;
2171 char *matchp = star_match[0];
2174 if (*patternp == NULLCHAR) {
2175 *index = leftover_start = bufp - buf;
2179 if (*bufp == NULLCHAR) return FALSE;
2180 if (*patternp == '*') {
2181 if (*bufp == *(patternp + 1)) {
2183 matchp = star_match[++star_count];
2187 } else if (*bufp == '\n' || *bufp == '\r') {
2189 if (*patternp == NULLCHAR)
2194 *matchp++ = *bufp++;
2198 if (*patternp != *bufp) return FALSE;
2205 SendToPlayer(data, length)
2209 int error, outCount;
2210 outCount = OutputToProcess(NoProc, data, length, &error);
2211 if (outCount < length) {
2212 DisplayFatalError(_("Error writing to display"), error, 1);
2217 PackHolding(packed, holding)
2229 switch (runlength) {
2240 sprintf(q, "%d", runlength);
2252 /* Telnet protocol requests from the front end */
2254 TelnetRequest(ddww, option)
2255 unsigned char ddww, option;
2257 unsigned char msg[3];
2258 int outCount, outError;
2260 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2262 if (appData.debugMode) {
2263 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2279 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2288 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2291 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2298 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2305 if (!appData.icsActive) return;
2306 TelnetRequest(TN_DO, TN_ECHO);
2312 if (!appData.icsActive) return;
2313 TelnetRequest(TN_DONT, TN_ECHO);
2317 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2319 /* put the holdings sent to us by the server on the board holdings area */
2320 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2324 if(gameInfo.holdingsWidth < 2) return;
2325 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2326 return; // prevent overwriting by pre-board holdings
2328 if( (int)lowestPiece >= BlackPawn ) {
2331 holdingsStartRow = BOARD_HEIGHT-1;
2334 holdingsColumn = BOARD_WIDTH-1;
2335 countsColumn = BOARD_WIDTH-2;
2336 holdingsStartRow = 0;
2340 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2341 board[i][holdingsColumn] = EmptySquare;
2342 board[i][countsColumn] = (ChessSquare) 0;
2344 while( (p=*holdings++) != NULLCHAR ) {
2345 piece = CharToPiece( ToUpper(p) );
2346 if(piece == EmptySquare) continue;
2347 /*j = (int) piece - (int) WhitePawn;*/
2348 j = PieceToNumber(piece);
2349 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2350 if(j < 0) continue; /* should not happen */
2351 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2352 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2353 board[holdingsStartRow+j*direction][countsColumn]++;
2359 VariantSwitch(Board board, VariantClass newVariant)
2361 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2362 static Board oldBoard;
2364 startedFromPositionFile = FALSE;
2365 if(gameInfo.variant == newVariant) return;
2367 /* [HGM] This routine is called each time an assignment is made to
2368 * gameInfo.variant during a game, to make sure the board sizes
2369 * are set to match the new variant. If that means adding or deleting
2370 * holdings, we shift the playing board accordingly
2371 * This kludge is needed because in ICS observe mode, we get boards
2372 * of an ongoing game without knowing the variant, and learn about the
2373 * latter only later. This can be because of the move list we requested,
2374 * in which case the game history is refilled from the beginning anyway,
2375 * but also when receiving holdings of a crazyhouse game. In the latter
2376 * case we want to add those holdings to the already received position.
2380 if (appData.debugMode) {
2381 fprintf(debugFP, "Switch board from %s to %s\n",
2382 VariantName(gameInfo.variant), VariantName(newVariant));
2383 setbuf(debugFP, NULL);
2385 shuffleOpenings = 0; /* [HGM] shuffle */
2386 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2390 newWidth = 9; newHeight = 9;
2391 gameInfo.holdingsSize = 7;
2392 case VariantBughouse:
2393 case VariantCrazyhouse:
2394 newHoldingsWidth = 2; break;
2398 newHoldingsWidth = 2;
2399 gameInfo.holdingsSize = 8;
2402 case VariantCapablanca:
2403 case VariantCapaRandom:
2406 newHoldingsWidth = gameInfo.holdingsSize = 0;
2409 if(newWidth != gameInfo.boardWidth ||
2410 newHeight != gameInfo.boardHeight ||
2411 newHoldingsWidth != gameInfo.holdingsWidth ) {
2413 /* shift position to new playing area, if needed */
2414 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2415 for(i=0; i<BOARD_HEIGHT; i++)
2416 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2417 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419 for(i=0; i<newHeight; i++) {
2420 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2421 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2423 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2424 for(i=0; i<BOARD_HEIGHT; i++)
2425 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2426 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429 gameInfo.boardWidth = newWidth;
2430 gameInfo.boardHeight = newHeight;
2431 gameInfo.holdingsWidth = newHoldingsWidth;
2432 gameInfo.variant = newVariant;
2433 InitDrawingSizes(-2, 0);
2434 } else gameInfo.variant = newVariant;
2435 CopyBoard(oldBoard, board); // remember correctly formatted board
2436 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2437 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2440 static int loggedOn = FALSE;
2442 /*-- Game start info cache: --*/
2444 char gs_kind[MSG_SIZ];
2445 static char player1Name[128] = "";
2446 static char player2Name[128] = "";
2447 static char cont_seq[] = "\n\\ ";
2448 static int player1Rating = -1;
2449 static int player2Rating = -1;
2450 /*----------------------------*/
2452 ColorClass curColor = ColorNormal;
2453 int suppressKibitz = 0;
2456 Boolean soughtPending = FALSE;
2457 Boolean seekGraphUp;
2458 #define MAX_SEEK_ADS 200
2460 char *seekAdList[MAX_SEEK_ADS];
2461 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2462 float tcList[MAX_SEEK_ADS];
2463 char colorList[MAX_SEEK_ADS];
2464 int nrOfSeekAds = 0;
2465 int minRating = 1010, maxRating = 2800;
2466 int hMargin = 10, vMargin = 20, h, w;
2467 extern int squareSize, lineGap;
2472 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2473 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2474 if(r < minRating+100 && r >=0 ) r = minRating+100;
2475 if(r > maxRating) r = maxRating;
2476 if(tc < 1.) tc = 1.;
2477 if(tc > 95.) tc = 95.;
2478 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2479 y = ((double)r - minRating)/(maxRating - minRating)
2480 * (h-vMargin-squareSize/8-1) + vMargin;
2481 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2482 if(strstr(seekAdList[i], " u ")) color = 1;
2483 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2484 !strstr(seekAdList[i], "bullet") &&
2485 !strstr(seekAdList[i], "blitz") &&
2486 !strstr(seekAdList[i], "standard") ) color = 2;
2487 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2488 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2492 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2494 char buf[MSG_SIZ], *ext = "";
2495 VariantClass v = StringToVariant(type);
2496 if(strstr(type, "wild")) {
2497 ext = type + 4; // append wild number
2498 if(v == VariantFischeRandom) type = "chess960"; else
2499 if(v == VariantLoadable) type = "setup"; else
2500 type = VariantName(v);
2502 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2503 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2504 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2505 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2506 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2507 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2508 seekNrList[nrOfSeekAds] = nr;
2509 zList[nrOfSeekAds] = 0;
2510 seekAdList[nrOfSeekAds++] = StrSave(buf);
2511 if(plot) PlotSeekAd(nrOfSeekAds-1);
2518 int x = xList[i], y = yList[i], d=squareSize/4, k;
2519 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2520 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2521 // now replot every dot that overlapped
2522 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2523 int xx = xList[k], yy = yList[k];
2524 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2525 DrawSeekDot(xx, yy, colorList[k]);
2530 RemoveSeekAd(int nr)
2533 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2535 if(seekAdList[i]) free(seekAdList[i]);
2536 seekAdList[i] = seekAdList[--nrOfSeekAds];
2537 seekNrList[i] = seekNrList[nrOfSeekAds];
2538 ratingList[i] = ratingList[nrOfSeekAds];
2539 colorList[i] = colorList[nrOfSeekAds];
2540 tcList[i] = tcList[nrOfSeekAds];
2541 xList[i] = xList[nrOfSeekAds];
2542 yList[i] = yList[nrOfSeekAds];
2543 zList[i] = zList[nrOfSeekAds];
2544 seekAdList[nrOfSeekAds] = NULL;
2550 MatchSoughtLine(char *line)
2552 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2553 int nr, base, inc, u=0; char dummy;
2555 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2556 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2558 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2559 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2560 // match: compact and save the line
2561 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2571 if(!seekGraphUp) return FALSE;
2572 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2573 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2575 DrawSeekBackground(0, 0, w, h);
2576 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2577 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2578 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2579 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2581 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2584 snprintf(buf, MSG_SIZ, "%d", i);
2585 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2588 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2589 for(i=1; i<100; i+=(i<10?1:5)) {
2590 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2591 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2592 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2594 snprintf(buf, MSG_SIZ, "%d", i);
2595 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2598 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2602 int SeekGraphClick(ClickType click, int x, int y, int moving)
2604 static int lastDown = 0, displayed = 0, lastSecond;
2605 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2606 if(click == Release || moving) return FALSE;
2608 soughtPending = TRUE;
2609 SendToICS(ics_prefix);
2610 SendToICS("sought\n"); // should this be "sought all"?
2611 } else { // issue challenge based on clicked ad
2612 int dist = 10000; int i, closest = 0, second = 0;
2613 for(i=0; i<nrOfSeekAds; i++) {
2614 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2615 if(d < dist) { dist = d; closest = i; }
2616 second += (d - zList[i] < 120); // count in-range ads
2617 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2621 second = (second > 1);
2622 if(displayed != closest || second != lastSecond) {
2623 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2624 lastSecond = second; displayed = closest;
2626 if(click == Press) {
2627 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2630 } // on press 'hit', only show info
2631 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2632 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2633 SendToICS(ics_prefix);
2635 return TRUE; // let incoming board of started game pop down the graph
2636 } else if(click == Release) { // release 'miss' is ignored
2637 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2638 if(moving == 2) { // right up-click
2639 nrOfSeekAds = 0; // refresh graph
2640 soughtPending = TRUE;
2641 SendToICS(ics_prefix);
2642 SendToICS("sought\n"); // should this be "sought all"?
2645 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2646 // press miss or release hit 'pop down' seek graph
2647 seekGraphUp = FALSE;
2648 DrawPosition(TRUE, NULL);
2654 read_from_ics(isr, closure, data, count, error)
2661 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2662 #define STARTED_NONE 0
2663 #define STARTED_MOVES 1
2664 #define STARTED_BOARD 2
2665 #define STARTED_OBSERVE 3
2666 #define STARTED_HOLDINGS 4
2667 #define STARTED_CHATTER 5
2668 #define STARTED_COMMENT 6
2669 #define STARTED_MOVES_NOHIDE 7
2671 static int started = STARTED_NONE;
2672 static char parse[20000];
2673 static int parse_pos = 0;
2674 static char buf[BUF_SIZE + 1];
2675 static int firstTime = TRUE, intfSet = FALSE;
2676 static ColorClass prevColor = ColorNormal;
2677 static int savingComment = FALSE;
2678 static int cmatch = 0; // continuation sequence match
2685 int backup; /* [DM] For zippy color lines */
2687 char talker[MSG_SIZ]; // [HGM] chat
2690 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2692 if (appData.debugMode) {
2694 fprintf(debugFP, "<ICS: ");
2695 show_bytes(debugFP, data, count);
2696 fprintf(debugFP, "\n");
2700 if (appData.debugMode) { int f = forwardMostMove;
2701 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2702 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2703 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2706 /* If last read ended with a partial line that we couldn't parse,
2707 prepend it to the new read and try again. */
2708 if (leftover_len > 0) {
2709 for (i=0; i<leftover_len; i++)
2710 buf[i] = buf[leftover_start + i];
2713 /* copy new characters into the buffer */
2714 bp = buf + leftover_len;
2715 buf_len=leftover_len;
2716 for (i=0; i<count; i++)
2719 if (data[i] == '\r')
2722 // join lines split by ICS?
2723 if (!appData.noJoin)
2726 Joining just consists of finding matches against the
2727 continuation sequence, and discarding that sequence
2728 if found instead of copying it. So, until a match
2729 fails, there's nothing to do since it might be the
2730 complete sequence, and thus, something we don't want
2733 if (data[i] == cont_seq[cmatch])
2736 if (cmatch == strlen(cont_seq))
2738 cmatch = 0; // complete match. just reset the counter
2741 it's possible for the ICS to not include the space
2742 at the end of the last word, making our [correct]
2743 join operation fuse two separate words. the server
2744 does this when the space occurs at the width setting.
2746 if (!buf_len || buf[buf_len-1] != ' ')
2757 match failed, so we have to copy what matched before
2758 falling through and copying this character. In reality,
2759 this will only ever be just the newline character, but
2760 it doesn't hurt to be precise.
2762 strncpy(bp, cont_seq, cmatch);
2774 buf[buf_len] = NULLCHAR;
2775 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780 while (i < buf_len) {
2781 /* Deal with part of the TELNET option negotiation
2782 protocol. We refuse to do anything beyond the
2783 defaults, except that we allow the WILL ECHO option,
2784 which ICS uses to turn off password echoing when we are
2785 directly connected to it. We reject this option
2786 if localLineEditing mode is on (always on in xboard)
2787 and we are talking to port 23, which might be a real
2788 telnet server that will try to keep WILL ECHO on permanently.
2790 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2791 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2792 unsigned char option;
2794 switch ((unsigned char) buf[++i]) {
2796 if (appData.debugMode)
2797 fprintf(debugFP, "\n<WILL ");
2798 switch (option = (unsigned char) buf[++i]) {
2800 if (appData.debugMode)
2801 fprintf(debugFP, "ECHO ");
2802 /* Reply only if this is a change, according
2803 to the protocol rules. */
2804 if (remoteEchoOption) break;
2805 if (appData.localLineEditing &&
2806 atoi(appData.icsPort) == TN_PORT) {
2807 TelnetRequest(TN_DONT, TN_ECHO);
2810 TelnetRequest(TN_DO, TN_ECHO);
2811 remoteEchoOption = TRUE;
2815 if (appData.debugMode)
2816 fprintf(debugFP, "%d ", option);
2817 /* Whatever this is, we don't want it. */
2818 TelnetRequest(TN_DONT, option);
2823 if (appData.debugMode)
2824 fprintf(debugFP, "\n<WONT ");
2825 switch (option = (unsigned char) buf[++i]) {
2827 if (appData.debugMode)
2828 fprintf(debugFP, "ECHO ");
2829 /* Reply only if this is a change, according
2830 to the protocol rules. */
2831 if (!remoteEchoOption) break;
2833 TelnetRequest(TN_DONT, TN_ECHO);
2834 remoteEchoOption = FALSE;
2837 if (appData.debugMode)
2838 fprintf(debugFP, "%d ", (unsigned char) option);
2839 /* Whatever this is, it must already be turned
2840 off, because we never agree to turn on
2841 anything non-default, so according to the
2842 protocol rules, we don't reply. */
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<DO ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 /* Whatever this is, we refuse to do it. */
2852 if (appData.debugMode)
2853 fprintf(debugFP, "%d ", option);
2854 TelnetRequest(TN_WONT, option);
2859 if (appData.debugMode)
2860 fprintf(debugFP, "\n<DONT ");
2861 switch (option = (unsigned char) buf[++i]) {
2863 if (appData.debugMode)
2864 fprintf(debugFP, "%d ", option);
2865 /* Whatever this is, we are already not doing
2866 it, because we never agree to do anything
2867 non-default, so according to the protocol
2868 rules, we don't reply. */
2873 if (appData.debugMode)
2874 fprintf(debugFP, "\n<IAC ");
2875 /* Doubled IAC; pass it through */
2879 if (appData.debugMode)
2880 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2881 /* Drop all other telnet commands on the floor */
2884 if (oldi > next_out)
2885 SendToPlayer(&buf[next_out], oldi - next_out);
2891 /* OK, this at least will *usually* work */
2892 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896 if (loggedOn && !intfSet) {
2897 if (ics_type == ICS_ICC) {
2898 snprintf(str, MSG_SIZ,
2899 "/set-quietly interface %s\n/set-quietly style 12\n",
2901 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902 strcat(str, "/set-2 51 1\n/set seek 1\n");
2903 } else if (ics_type == ICS_CHESSNET) {
2904 snprintf(str, MSG_SIZ, "/style 12\n");
2906 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2907 strcat(str, programVersion);
2908 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2909 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2910 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2912 strcat(str, "$iset nohighlight 1\n");
2914 strcat(str, "$iset lock 1\n$style 12\n");
2917 NotifyFrontendLogin();
2921 if (started == STARTED_COMMENT) {
2922 /* Accumulate characters in comment */
2923 parse[parse_pos++] = buf[i];
2924 if (buf[i] == '\n') {
2925 parse[parse_pos] = NULLCHAR;
2926 if(chattingPartner>=0) {
2928 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2929 OutputChatMessage(chattingPartner, mess);
2930 chattingPartner = -1;
2931 next_out = i+1; // [HGM] suppress printing in ICS window
2933 if(!suppressKibitz) // [HGM] kibitz
2934 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2935 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2936 int nrDigit = 0, nrAlph = 0, j;
2937 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2938 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2939 parse[parse_pos] = NULLCHAR;
2940 // try to be smart: if it does not look like search info, it should go to
2941 // ICS interaction window after all, not to engine-output window.
2942 for(j=0; j<parse_pos; j++) { // count letters and digits
2943 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2944 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2945 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2947 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2948 int depth=0; float score;
2949 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2950 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2951 pvInfoList[forwardMostMove-1].depth = depth;
2952 pvInfoList[forwardMostMove-1].score = 100*score;
2954 OutputKibitz(suppressKibitz, parse);
2957 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2958 SendToPlayer(tmp, strlen(tmp));
2960 next_out = i+1; // [HGM] suppress printing in ICS window
2962 started = STARTED_NONE;
2964 /* Don't match patterns against characters in comment */
2969 if (started == STARTED_CHATTER) {
2970 if (buf[i] != '\n') {
2971 /* Don't match patterns against characters in chatter */
2975 started = STARTED_NONE;
2976 if(suppressKibitz) next_out = i+1;
2979 /* Kludge to deal with rcmd protocol */
2980 if (firstTime && looking_at(buf, &i, "\001*")) {
2981 DisplayFatalError(&buf[1], 0, 1);
2987 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2990 if (appData.debugMode)
2991 fprintf(debugFP, "ics_type %d\n", ics_type);
2994 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2995 ics_type = ICS_FICS;
2997 if (appData.debugMode)
2998 fprintf(debugFP, "ics_type %d\n", ics_type);
3001 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3002 ics_type = ICS_CHESSNET;
3004 if (appData.debugMode)
3005 fprintf(debugFP, "ics_type %d\n", ics_type);
3010 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3011 looking_at(buf, &i, "Logging you in as \"*\"") ||
3012 looking_at(buf, &i, "will be \"*\""))) {
3013 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3017 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3019 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3020 DisplayIcsInteractionTitle(buf);
3021 have_set_title = TRUE;
3024 /* skip finger notes */
3025 if (started == STARTED_NONE &&
3026 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3027 (buf[i] == '1' && buf[i+1] == '0')) &&
3028 buf[i+2] == ':' && buf[i+3] == ' ') {
3029 started = STARTED_CHATTER;
3035 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3036 if(appData.seekGraph) {
3037 if(soughtPending && MatchSoughtLine(buf+i)) {
3038 i = strstr(buf+i, "rated") - buf;
3039 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040 next_out = leftover_start = i;
3041 started = STARTED_CHATTER;
3042 suppressKibitz = TRUE;
3045 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3046 && looking_at(buf, &i, "* ads displayed")) {
3047 soughtPending = FALSE;
3052 if(appData.autoRefresh) {
3053 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3054 int s = (ics_type == ICS_ICC); // ICC format differs
3056 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3057 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3058 looking_at(buf, &i, "*% "); // eat prompt
3059 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3060 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061 next_out = i; // suppress
3064 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3065 char *p = star_match[0];
3067 if(seekGraphUp) RemoveSeekAd(atoi(p));
3068 while(*p && *p++ != ' '); // next
3070 looking_at(buf, &i, "*% "); // eat prompt
3071 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078 /* skip formula vars */
3079 if (started == STARTED_NONE &&
3080 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3081 started = STARTED_CHATTER;
3086 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3087 if (appData.autoKibitz && started == STARTED_NONE &&
3088 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3089 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3090 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3091 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3092 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3093 suppressKibitz = TRUE;
3094 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3097 && (gameMode == IcsPlayingWhite)) ||
3098 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3099 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3100 started = STARTED_CHATTER; // own kibitz we simply discard
3102 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3103 parse_pos = 0; parse[0] = NULLCHAR;
3104 savingComment = TRUE;
3105 suppressKibitz = gameMode != IcsObserving ? 2 :
3106 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3110 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3111 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3112 && atoi(star_match[0])) {
3113 // suppress the acknowledgements of our own autoKibitz
3115 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3116 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3117 SendToPlayer(star_match[0], strlen(star_match[0]));
3118 if(looking_at(buf, &i, "*% ")) // eat prompt
3119 suppressKibitz = FALSE;
3123 } // [HGM] kibitz: end of patch
3125 // [HGM] chat: intercept tells by users for which we have an open chat window
3127 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3128 looking_at(buf, &i, "* whispers:") ||
3129 looking_at(buf, &i, "* kibitzes:") ||
3130 looking_at(buf, &i, "* shouts:") ||
3131 looking_at(buf, &i, "* c-shouts:") ||
3132 looking_at(buf, &i, "--> * ") ||
3133 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3134 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3135 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3136 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3138 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3139 chattingPartner = -1;
3141 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3142 for(p=0; p<MAX_CHAT; p++) {
3143 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3144 talker[0] = '['; strcat(talker, "] ");
3145 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3146 chattingPartner = p; break;
3149 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3150 for(p=0; p<MAX_CHAT; p++) {
3151 if(!strcmp("kibitzes", chatPartner[p])) {
3152 talker[0] = '['; strcat(talker, "] ");
3153 chattingPartner = p; break;
3156 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3157 for(p=0; p<MAX_CHAT; p++) {
3158 if(!strcmp("whispers", chatPartner[p])) {
3159 talker[0] = '['; strcat(talker, "] ");
3160 chattingPartner = p; break;
3163 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3164 if(buf[i-8] == '-' && buf[i-3] == 't')
3165 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3166 if(!strcmp("c-shouts", chatPartner[p])) {
3167 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3168 chattingPartner = p; break;
3171 if(chattingPartner < 0)
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(!strcmp("shouts", chatPartner[p])) {
3174 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3175 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3176 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3177 chattingPartner = p; break;
3181 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3182 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3183 talker[0] = 0; Colorize(ColorTell, FALSE);
3184 chattingPartner = p; break;
3186 if(chattingPartner<0) i = oldi; else {
3187 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3188 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3189 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190 started = STARTED_COMMENT;
3191 parse_pos = 0; parse[0] = NULLCHAR;
3192 savingComment = 3 + chattingPartner; // counts as TRUE
3193 suppressKibitz = TRUE;
3196 } // [HGM] chat: end of patch
3199 if (appData.zippyTalk || appData.zippyPlay) {
3200 /* [DM] Backup address for color zippy lines */
3202 if (loggedOn == TRUE)
3203 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3204 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3206 } // [DM] 'else { ' deleted
3208 /* Regular tells and says */
3209 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3210 looking_at(buf, &i, "* (your partner) tells you: ") ||
3211 looking_at(buf, &i, "* says: ") ||
3212 /* Don't color "message" or "messages" output */
3213 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3214 looking_at(buf, &i, "*. * at *:*: ") ||
3215 looking_at(buf, &i, "--* (*:*): ") ||
3216 /* Message notifications (same color as tells) */
3217 looking_at(buf, &i, "* has left a message ") ||
3218 looking_at(buf, &i, "* just sent you a message:\n") ||
3219 /* Whispers and kibitzes */
3220 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3221 looking_at(buf, &i, "* kibitzes: ") ||
3223 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3225 if (tkind == 1 && strchr(star_match[0], ':')) {
3226 /* Avoid "tells you:" spoofs in channels */
3229 if (star_match[0][0] == NULLCHAR ||
3230 strchr(star_match[0], ' ') ||
3231 (tkind == 3 && strchr(star_match[1], ' '))) {
3232 /* Reject bogus matches */
3235 if (appData.colorize) {
3236 if (oldi > next_out) {
3237 SendToPlayer(&buf[next_out], oldi - next_out);
3242 Colorize(ColorTell, FALSE);
3243 curColor = ColorTell;
3246 Colorize(ColorKibitz, FALSE);
3247 curColor = ColorKibitz;
3250 p = strrchr(star_match[1], '(');
3257 Colorize(ColorChannel1, FALSE);
3258 curColor = ColorChannel1;
3260 Colorize(ColorChannel, FALSE);
3261 curColor = ColorChannel;
3265 curColor = ColorNormal;
3269 if (started == STARTED_NONE && appData.autoComment &&
3270 (gameMode == IcsObserving ||
3271 gameMode == IcsPlayingWhite ||
3272 gameMode == IcsPlayingBlack)) {
3273 parse_pos = i - oldi;
3274 memcpy(parse, &buf[oldi], parse_pos);
3275 parse[parse_pos] = NULLCHAR;
3276 started = STARTED_COMMENT;
3277 savingComment = TRUE;
3279 started = STARTED_CHATTER;
3280 savingComment = FALSE;
3287 if (looking_at(buf, &i, "* s-shouts: ") ||
3288 looking_at(buf, &i, "* c-shouts: ")) {
3289 if (appData.colorize) {
3290 if (oldi > next_out) {
3291 SendToPlayer(&buf[next_out], oldi - next_out);
3294 Colorize(ColorSShout, FALSE);
3295 curColor = ColorSShout;
3298 started = STARTED_CHATTER;
3302 if (looking_at(buf, &i, "--->")) {
3307 if (looking_at(buf, &i, "* shouts: ") ||
3308 looking_at(buf, &i, "--> ")) {
3309 if (appData.colorize) {
3310 if (oldi > next_out) {
3311 SendToPlayer(&buf[next_out], oldi - next_out);
3314 Colorize(ColorShout, FALSE);
3315 curColor = ColorShout;
3318 started = STARTED_CHATTER;
3322 if (looking_at( buf, &i, "Challenge:")) {
3323 if (appData.colorize) {
3324 if (oldi > next_out) {
3325 SendToPlayer(&buf[next_out], oldi - next_out);
3328 Colorize(ColorChallenge, FALSE);
3329 curColor = ColorChallenge;
3335 if (looking_at(buf, &i, "* offers you") ||
3336 looking_at(buf, &i, "* offers to be") ||
3337 looking_at(buf, &i, "* would like to") ||
3338 looking_at(buf, &i, "* requests to") ||
3339 looking_at(buf, &i, "Your opponent offers") ||
3340 looking_at(buf, &i, "Your opponent requests")) {
3342 if (appData.colorize) {
3343 if (oldi > next_out) {
3344 SendToPlayer(&buf[next_out], oldi - next_out);
3347 Colorize(ColorRequest, FALSE);
3348 curColor = ColorRequest;
3353 if (looking_at(buf, &i, "* (*) seeking")) {
3354 if (appData.colorize) {
3355 if (oldi > next_out) {
3356 SendToPlayer(&buf[next_out], oldi - next_out);
3359 Colorize(ColorSeek, FALSE);
3360 curColor = ColorSeek;
3365 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3367 if (looking_at(buf, &i, "\\ ")) {
3368 if (prevColor != ColorNormal) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(prevColor, TRUE);
3374 curColor = prevColor;
3376 if (savingComment) {
3377 parse_pos = i - oldi;
3378 memcpy(parse, &buf[oldi], parse_pos);
3379 parse[parse_pos] = NULLCHAR;
3380 started = STARTED_COMMENT;
3381 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3382 chattingPartner = savingComment - 3; // kludge to remember the box
3384 started = STARTED_CHATTER;
3389 if (looking_at(buf, &i, "Black Strength :") ||
3390 looking_at(buf, &i, "<<< style 10 board >>>") ||
3391 looking_at(buf, &i, "<10>") ||
3392 looking_at(buf, &i, "#@#")) {
3393 /* Wrong board style */
3395 SendToICS(ics_prefix);
3396 SendToICS("set style 12\n");
3397 SendToICS(ics_prefix);
3398 SendToICS("refresh\n");
3402 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3404 have_sent_ICS_logon = 1;
3408 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3409 (looking_at(buf, &i, "\n<12> ") ||
3410 looking_at(buf, &i, "<12> "))) {
3412 if (oldi > next_out) {
3413 SendToPlayer(&buf[next_out], oldi - next_out);
3416 started = STARTED_BOARD;
3421 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3422 looking_at(buf, &i, "<b1> ")) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 started = STARTED_HOLDINGS;
3432 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3434 /* Header for a move list -- first line */
3436 switch (ics_getting_history) {
3440 case BeginningOfGame:
3441 /* User typed "moves" or "oldmoves" while we
3442 were idle. Pretend we asked for these
3443 moves and soak them up so user can step
3444 through them and/or save them.
3447 gameMode = IcsObserving;
3450 ics_getting_history = H_GOT_UNREQ_HEADER;
3452 case EditGame: /*?*/
3453 case EditPosition: /*?*/
3454 /* Should above feature work in these modes too? */
3455 /* For now it doesn't */
3456 ics_getting_history = H_GOT_UNWANTED_HEADER;
3459 ics_getting_history = H_GOT_UNWANTED_HEADER;
3464 /* Is this the right one? */
3465 if (gameInfo.white && gameInfo.black &&
3466 strcmp(gameInfo.white, star_match[0]) == 0 &&
3467 strcmp(gameInfo.black, star_match[2]) == 0) {
3469 ics_getting_history = H_GOT_REQ_HEADER;
3472 case H_GOT_REQ_HEADER:
3473 case H_GOT_UNREQ_HEADER:
3474 case H_GOT_UNWANTED_HEADER:
3475 case H_GETTING_MOVES:
3476 /* Should not happen */
3477 DisplayError(_("Error gathering move list: two headers"), 0);
3478 ics_getting_history = H_FALSE;
3482 /* Save player ratings into gameInfo if needed */
3483 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3484 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3485 (gameInfo.whiteRating == -1 ||
3486 gameInfo.blackRating == -1)) {
3488 gameInfo.whiteRating = string_to_rating(star_match[1]);
3489 gameInfo.blackRating = string_to_rating(star_match[3]);
3490 if (appData.debugMode)
3491 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3492 gameInfo.whiteRating, gameInfo.blackRating);
3497 if (looking_at(buf, &i,
3498 "* * match, initial time: * minute*, increment: * second")) {
3499 /* Header for a move list -- second line */
3500 /* Initial board will follow if this is a wild game */
3501 if (gameInfo.event != NULL) free(gameInfo.event);
3502 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3503 gameInfo.event = StrSave(str);
3504 /* [HGM] we switched variant. Translate boards if needed. */
3505 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3509 if (looking_at(buf, &i, "Move ")) {
3510 /* Beginning of a move list */
3511 switch (ics_getting_history) {
3513 /* Normally should not happen */
3514 /* Maybe user hit reset while we were parsing */
3517 /* Happens if we are ignoring a move list that is not
3518 * the one we just requested. Common if the user
3519 * tries to observe two games without turning off
3522 case H_GETTING_MOVES:
3523 /* Should not happen */
3524 DisplayError(_("Error gathering move list: nested"), 0);
3525 ics_getting_history = H_FALSE;
3527 case H_GOT_REQ_HEADER:
3528 ics_getting_history = H_GETTING_MOVES;
3529 started = STARTED_MOVES;
3531 if (oldi > next_out) {
3532 SendToPlayer(&buf[next_out], oldi - next_out);
3535 case H_GOT_UNREQ_HEADER:
3536 ics_getting_history = H_GETTING_MOVES;
3537 started = STARTED_MOVES_NOHIDE;
3540 case H_GOT_UNWANTED_HEADER:
3541 ics_getting_history = H_FALSE;
3547 if (looking_at(buf, &i, "% ") ||
3548 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3549 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3550 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3551 soughtPending = FALSE;
3555 if(suppressKibitz) next_out = i;
3556 savingComment = FALSE;
3560 case STARTED_MOVES_NOHIDE:
3561 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3562 parse[parse_pos + i - oldi] = NULLCHAR;
3563 ParseGameHistory(parse);
3565 if (appData.zippyPlay && first.initDone) {
3566 FeedMovesToProgram(&first, forwardMostMove);
3567 if (gameMode == IcsPlayingWhite) {
3568 if (WhiteOnMove(forwardMostMove)) {
3569 if (first.sendTime) {
3570 if (first.useColors) {
3571 SendToProgram("black\n", &first);
3573 SendTimeRemaining(&first, TRUE);
3575 if (first.useColors) {
3576 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3578 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3579 first.maybeThinking = TRUE;
3581 if (first.usePlayother) {
3582 if (first.sendTime) {
3583 SendTimeRemaining(&first, TRUE);
3585 SendToProgram("playother\n", &first);
3591 } else if (gameMode == IcsPlayingBlack) {
3592 if (!WhiteOnMove(forwardMostMove)) {
3593 if (first.sendTime) {
3594 if (first.useColors) {
3595 SendToProgram("white\n", &first);
3597 SendTimeRemaining(&first, FALSE);
3599 if (first.useColors) {
3600 SendToProgram("black\n", &first);
3602 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3603 first.maybeThinking = TRUE;
3605 if (first.usePlayother) {
3606 if (first.sendTime) {
3607 SendTimeRemaining(&first, FALSE);
3609 SendToProgram("playother\n", &first);
3618 if (gameMode == IcsObserving && ics_gamenum == -1) {
3619 /* Moves came from oldmoves or moves command
3620 while we weren't doing anything else.
3622 currentMove = forwardMostMove;
3623 ClearHighlights();/*!!could figure this out*/
3624 flipView = appData.flipView;
3625 DrawPosition(TRUE, boards[currentMove]);
3626 DisplayBothClocks();
3627 snprintf(str, MSG_SIZ, "%s vs. %s",
3628 gameInfo.white, gameInfo.black);
3632 /* Moves were history of an active game */
3633 if (gameInfo.resultDetails != NULL) {
3634 free(gameInfo.resultDetails);
3635 gameInfo.resultDetails = NULL;
3638 HistorySet(parseList, backwardMostMove,
3639 forwardMostMove, currentMove-1);
3640 DisplayMove(currentMove - 1);
3641 if (started == STARTED_MOVES) next_out = i;
3642 started = STARTED_NONE;
3643 ics_getting_history = H_FALSE;
3646 case STARTED_OBSERVE:
3647 started = STARTED_NONE;
3648 SendToICS(ics_prefix);
3649 SendToICS("refresh\n");
3655 if(bookHit) { // [HGM] book: simulate book reply
3656 static char bookMove[MSG_SIZ]; // a bit generous?
3658 programStats.nodes = programStats.depth = programStats.time =
3659 programStats.score = programStats.got_only_move = 0;
3660 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3662 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3663 strcat(bookMove, bookHit);
3664 HandleMachineMove(bookMove, &first);
3669 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3670 started == STARTED_HOLDINGS ||
3671 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3672 /* Accumulate characters in move list or board */
3673 parse[parse_pos++] = buf[i];
3676 /* Start of game messages. Mostly we detect start of game
3677 when the first board image arrives. On some versions
3678 of the ICS, though, we need to do a "refresh" after starting
3679 to observe in order to get the current board right away. */
3680 if (looking_at(buf, &i, "Adding game * to observation list")) {
3681 started = STARTED_OBSERVE;
3685 /* Handle auto-observe */
3686 if (appData.autoObserve &&
3687 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3688 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3690 /* Choose the player that was highlighted, if any. */
3691 if (star_match[0][0] == '\033' ||
3692 star_match[1][0] != '\033') {
3693 player = star_match[0];
3695 player = star_match[2];
3697 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3698 ics_prefix, StripHighlightAndTitle(player));
3701 /* Save ratings from notify string */
3702 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3703 player1Rating = string_to_rating(star_match[1]);
3704 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3705 player2Rating = string_to_rating(star_match[3]);
3707 if (appData.debugMode)
3709 "Ratings from 'Game notification:' %s %d, %s %d\n",
3710 player1Name, player1Rating,
3711 player2Name, player2Rating);
3716 /* Deal with automatic examine mode after a game,
3717 and with IcsObserving -> IcsExamining transition */
3718 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3719 looking_at(buf, &i, "has made you an examiner of game *")) {
3721 int gamenum = atoi(star_match[0]);
3722 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3723 gamenum == ics_gamenum) {
3724 /* We were already playing or observing this game;
3725 no need to refetch history */
3726 gameMode = IcsExamining;
3728 pauseExamForwardMostMove = forwardMostMove;
3729 } else if (currentMove < forwardMostMove) {
3730 ForwardInner(forwardMostMove);
3733 /* I don't think this case really can happen */
3734 SendToICS(ics_prefix);
3735 SendToICS("refresh\n");
3740 /* Error messages */
3741 // if (ics_user_moved) {
3742 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3743 if (looking_at(buf, &i, "Illegal move") ||
3744 looking_at(buf, &i, "Not a legal move") ||
3745 looking_at(buf, &i, "Your king is in check") ||
3746 looking_at(buf, &i, "It isn't your turn") ||
3747 looking_at(buf, &i, "It is not your move")) {
3749 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3750 currentMove = forwardMostMove-1;
3751 DisplayMove(currentMove - 1); /* before DMError */
3752 DrawPosition(FALSE, boards[currentMove]);
3753 SwitchClocks(forwardMostMove-1); // [HGM] race
3754 DisplayBothClocks();
3756 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3762 if (looking_at(buf, &i, "still have time") ||
3763 looking_at(buf, &i, "not out of time") ||
3764 looking_at(buf, &i, "either player is out of time") ||
3765 looking_at(buf, &i, "has timeseal; checking")) {
3766 /* We must have called his flag a little too soon */
3767 whiteFlag = blackFlag = FALSE;
3771 if (looking_at(buf, &i, "added * seconds to") ||
3772 looking_at(buf, &i, "seconds were added to")) {
3773 /* Update the clocks */
3774 SendToICS(ics_prefix);
3775 SendToICS("refresh\n");
3779 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3780 ics_clock_paused = TRUE;
3785 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3786 ics_clock_paused = FALSE;
3791 /* Grab player ratings from the Creating: message.
3792 Note we have to check for the special case when
3793 the ICS inserts things like [white] or [black]. */
3794 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3795 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3797 0 player 1 name (not necessarily white)
3799 2 empty, white, or black (IGNORED)
3800 3 player 2 name (not necessarily black)
3803 The names/ratings are sorted out when the game
3804 actually starts (below).
3806 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3807 player1Rating = string_to_rating(star_match[1]);
3808 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3809 player2Rating = string_to_rating(star_match[4]);
3811 if (appData.debugMode)
3813 "Ratings from 'Creating:' %s %d, %s %d\n",
3814 player1Name, player1Rating,
3815 player2Name, player2Rating);
3820 /* Improved generic start/end-of-game messages */
3821 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3822 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3823 /* If tkind == 0: */
3824 /* star_match[0] is the game number */
3825 /* [1] is the white player's name */
3826 /* [2] is the black player's name */
3827 /* For end-of-game: */
3828 /* [3] is the reason for the game end */
3829 /* [4] is a PGN end game-token, preceded by " " */
3830 /* For start-of-game: */
3831 /* [3] begins with "Creating" or "Continuing" */
3832 /* [4] is " *" or empty (don't care). */
3833 int gamenum = atoi(star_match[0]);
3834 char *whitename, *blackname, *why, *endtoken;
3835 ChessMove endtype = EndOfFile;
3838 whitename = star_match[1];
3839 blackname = star_match[2];
3840 why = star_match[3];
3841 endtoken = star_match[4];
3843 whitename = star_match[1];
3844 blackname = star_match[3];
3845 why = star_match[5];
3846 endtoken = star_match[6];
3849 /* Game start messages */
3850 if (strncmp(why, "Creating ", 9) == 0 ||
3851 strncmp(why, "Continuing ", 11) == 0) {
3852 gs_gamenum = gamenum;
3853 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3854 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3856 if (appData.zippyPlay) {
3857 ZippyGameStart(whitename, blackname);
3860 partnerBoardValid = FALSE; // [HGM] bughouse
3864 /* Game end messages */
3865 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3866 ics_gamenum != gamenum) {
3869 while (endtoken[0] == ' ') endtoken++;
3870 switch (endtoken[0]) {
3873 endtype = GameUnfinished;
3876 endtype = BlackWins;
3879 if (endtoken[1] == '/')
3880 endtype = GameIsDrawn;
3882 endtype = WhiteWins;
3885 GameEnds(endtype, why, GE_ICS);
3887 if (appData.zippyPlay && first.initDone) {
3888 ZippyGameEnd(endtype, why);
3889 if (first.pr == NULL) {
3890 /* Start the next process early so that we'll
3891 be ready for the next challenge */
3892 StartChessProgram(&first);
3894 /* Send "new" early, in case this command takes
3895 a long time to finish, so that we'll be ready
3896 for the next challenge. */
3897 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3901 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3905 if (looking_at(buf, &i, "Removing game * from observation") ||
3906 looking_at(buf, &i, "no longer observing game *") ||
3907 looking_at(buf, &i, "Game * (*) has no examiners")) {
3908 if (gameMode == IcsObserving &&
3909 atoi(star_match[0]) == ics_gamenum)
3911 /* icsEngineAnalyze */
3912 if (appData.icsEngineAnalyze) {
3919 ics_user_moved = FALSE;
3924 if (looking_at(buf, &i, "no longer examining game *")) {
3925 if (gameMode == IcsExamining &&
3926 atoi(star_match[0]) == ics_gamenum)
3930 ics_user_moved = FALSE;
3935 /* Advance leftover_start past any newlines we find,
3936 so only partial lines can get reparsed */
3937 if (looking_at(buf, &i, "\n")) {
3938 prevColor = curColor;
3939 if (curColor != ColorNormal) {
3940 if (oldi > next_out) {
3941 SendToPlayer(&buf[next_out], oldi - next_out);
3944 Colorize(ColorNormal, FALSE);
3945 curColor = ColorNormal;
3947 if (started == STARTED_BOARD) {
3948 started = STARTED_NONE;
3949 parse[parse_pos] = NULLCHAR;
3950 ParseBoard12(parse);
3953 /* Send premove here */
3954 if (appData.premove) {
3956 if (currentMove == 0 &&
3957 gameMode == IcsPlayingWhite &&
3958 appData.premoveWhite) {
3959 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3960 if (appData.debugMode)
3961 fprintf(debugFP, "Sending premove:\n");
3963 } else if (currentMove == 1 &&
3964 gameMode == IcsPlayingBlack &&
3965 appData.premoveBlack) {
3966 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3967 if (appData.debugMode)
3968 fprintf(debugFP, "Sending premove:\n");
3970 } else if (gotPremove) {
3972 ClearPremoveHighlights();
3973 if (appData.debugMode)
3974 fprintf(debugFP, "Sending premove:\n");
3975 UserMoveEvent(premoveFromX, premoveFromY,
3976 premoveToX, premoveToY,
3981 /* Usually suppress following prompt */
3982 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3983 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3984 if (looking_at(buf, &i, "*% ")) {
3985 savingComment = FALSE;
3990 } else if (started == STARTED_HOLDINGS) {
3992 char new_piece[MSG_SIZ];
3993 started = STARTED_NONE;
3994 parse[parse_pos] = NULLCHAR;
3995 if (appData.debugMode)
3996 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3997 parse, currentMove);
3998 if (sscanf(parse, " game %d", &gamenum) == 1) {
3999 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4000 if (gameInfo.variant == VariantNormal) {
4001 /* [HGM] We seem to switch variant during a game!
4002 * Presumably no holdings were displayed, so we have
4003 * to move the position two files to the right to
4004 * create room for them!
4006 VariantClass newVariant;
4007 switch(gameInfo.boardWidth) { // base guess on board width
4008 case 9: newVariant = VariantShogi; break;
4009 case 10: newVariant = VariantGreat; break;
4010 default: newVariant = VariantCrazyhouse; break;
4012 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4013 /* Get a move list just to see the header, which
4014 will tell us whether this is really bug or zh */
4015 if (ics_getting_history == H_FALSE) {
4016 ics_getting_history = H_REQUESTED;
4017 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4021 new_piece[0] = NULLCHAR;
4022 sscanf(parse, "game %d white [%s black [%s <- %s",
4023 &gamenum, white_holding, black_holding,
4025 white_holding[strlen(white_holding)-1] = NULLCHAR;
4026 black_holding[strlen(black_holding)-1] = NULLCHAR;
4027 /* [HGM] copy holdings to board holdings area */
4028 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4029 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4030 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4032 if (appData.zippyPlay && first.initDone) {
4033 ZippyHoldings(white_holding, black_holding,
4037 if (tinyLayout || smallLayout) {
4038 char wh[16], bh[16];
4039 PackHolding(wh, white_holding);
4040 PackHolding(bh, black_holding);
4041 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4042 gameInfo.white, gameInfo.black);
4044 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4045 gameInfo.white, white_holding,
4046 gameInfo.black, black_holding);
4048 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4049 DrawPosition(FALSE, boards[currentMove]);
4051 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4052 sscanf(parse, "game %d white [%s black [%s <- %s",
4053 &gamenum, white_holding, black_holding,
4055 white_holding[strlen(white_holding)-1] = NULLCHAR;
4056 black_holding[strlen(black_holding)-1] = NULLCHAR;
4057 /* [HGM] copy holdings to partner-board holdings area */
4058 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4059 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4060 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4061 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4062 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4065 /* Suppress following prompt */
4066 if (looking_at(buf, &i, "*% ")) {
4067 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4068 savingComment = FALSE;
4076 i++; /* skip unparsed character and loop back */
4079 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4080 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4081 // SendToPlayer(&buf[next_out], i - next_out);
4082 started != STARTED_HOLDINGS && leftover_start > next_out) {
4083 SendToPlayer(&buf[next_out], leftover_start - next_out);
4087 leftover_len = buf_len - leftover_start;
4088 /* if buffer ends with something we couldn't parse,
4089 reparse it after appending the next read */
4091 } else if (count == 0) {
4092 RemoveInputSource(isr);
4093 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4095 DisplayFatalError(_("Error reading from ICS"), error, 1);
4100 /* Board style 12 looks like this:
4102 <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
4104 * The "<12> " is stripped before it gets to this routine. The two
4105 * trailing 0's (flip state and clock ticking) are later addition, and
4106 * some chess servers may not have them, or may have only the first.
4107 * Additional trailing fields may be added in the future.
4110 #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"
4112 #define RELATION_OBSERVING_PLAYED 0
4113 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4114 #define RELATION_PLAYING_MYMOVE 1
4115 #define RELATION_PLAYING_NOTMYMOVE -1
4116 #define RELATION_EXAMINING 2
4117 #define RELATION_ISOLATED_BOARD -3
4118 #define RELATION_STARTING_POSITION -4 /* FICS only */
4121 ParseBoard12(string)
4124 GameMode newGameMode;
4125 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4126 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4127 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4128 char to_play, board_chars[200];
4129 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4130 char black[32], white[32];
4132 int prevMove = currentMove;
4135 int fromX, fromY, toX, toY;
4137 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4138 char *bookHit = NULL; // [HGM] book
4139 Boolean weird = FALSE, reqFlag = FALSE;
4141 fromX = fromY = toX = toY = -1;
4145 if (appData.debugMode)
4146 fprintf(debugFP, _("Parsing board: %s\n"), string);
4148 move_str[0] = NULLCHAR;
4149 elapsed_time[0] = NULLCHAR;
4150 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4152 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4153 if(string[i] == ' ') { ranks++; files = 0; }
4155 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4158 for(j = 0; j <i; j++) board_chars[j] = string[j];
4159 board_chars[i] = '\0';
4162 n = sscanf(string, PATTERN, &to_play, &double_push,
4163 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4164 &gamenum, white, black, &relation, &basetime, &increment,
4165 &white_stren, &black_stren, &white_time, &black_time,
4166 &moveNum, str, elapsed_time, move_str, &ics_flip,
4170 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4171 DisplayError(str, 0);
4175 /* Convert the move number to internal form */
4176 moveNum = (moveNum - 1) * 2;
4177 if (to_play == 'B') moveNum++;
4178 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4179 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185 case RELATION_OBSERVING_PLAYED:
4186 case RELATION_OBSERVING_STATIC:
4187 if (gamenum == -1) {
4188 /* Old ICC buglet */
4189 relation = RELATION_OBSERVING_STATIC;
4191 newGameMode = IcsObserving;
4193 case RELATION_PLAYING_MYMOVE:
4194 case RELATION_PLAYING_NOTMYMOVE:
4196 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4197 IcsPlayingWhite : IcsPlayingBlack;
4199 case RELATION_EXAMINING:
4200 newGameMode = IcsExamining;
4202 case RELATION_ISOLATED_BOARD:
4204 /* Just display this board. If user was doing something else,
4205 we will forget about it until the next board comes. */
4206 newGameMode = IcsIdle;
4208 case RELATION_STARTING_POSITION:
4209 newGameMode = gameMode;
4213 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4214 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4215 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4217 for (k = 0; k < ranks; k++) {
4218 for (j = 0; j < files; j++)
4219 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4220 if(gameInfo.holdingsWidth > 1) {
4221 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4222 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4225 CopyBoard(partnerBoard, board);
4226 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4227 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4228 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4229 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4230 if(toSqr = strchr(str, '-')) {
4231 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4232 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4233 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4234 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4235 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4236 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4237 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4238 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4239 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4240 DisplayMessage(partnerStatus, "");
4241 partnerBoardValid = TRUE;
4245 /* Modify behavior for initial board display on move listing
4248 switch (ics_getting_history) {
4252 case H_GOT_REQ_HEADER:
4253 case H_GOT_UNREQ_HEADER:
4254 /* This is the initial position of the current game */
4255 gamenum = ics_gamenum;
4256 moveNum = 0; /* old ICS bug workaround */
4257 if (to_play == 'B') {
4258 startedFromSetupPosition = TRUE;
4259 blackPlaysFirst = TRUE;
4261 if (forwardMostMove == 0) forwardMostMove = 1;
4262 if (backwardMostMove == 0) backwardMostMove = 1;
4263 if (currentMove == 0) currentMove = 1;
4265 newGameMode = gameMode;
4266 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4268 case H_GOT_UNWANTED_HEADER:
4269 /* This is an initial board that we don't want */
4271 case H_GETTING_MOVES:
4272 /* Should not happen */
4273 DisplayError(_("Error gathering move list: extra board"), 0);
4274 ics_getting_history = H_FALSE;
4278 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4279 weird && (int)gameInfo.variant < (int)VariantShogi) {
4280 /* [HGM] We seem to have switched variant unexpectedly
4281 * Try to guess new variant from board size
4283 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4284 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4285 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4286 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4287 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4288 if(!weird) newVariant = VariantNormal;
4289 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4290 /* Get a move list just to see the header, which
4291 will tell us whether this is really bug or zh */
4292 if (ics_getting_history == H_FALSE) {
4293 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4294 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4299 /* Take action if this is the first board of a new game, or of a
4300 different game than is currently being displayed. */
4301 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4302 relation == RELATION_ISOLATED_BOARD) {
4304 /* Forget the old game and get the history (if any) of the new one */
4305 if (gameMode != BeginningOfGame) {
4309 if (appData.autoRaiseBoard) BoardToTop();
4311 if (gamenum == -1) {
4312 newGameMode = IcsIdle;
4313 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4314 appData.getMoveList && !reqFlag) {
4315 /* Need to get game history */
4316 ics_getting_history = H_REQUESTED;
4317 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4321 /* Initially flip the board to have black on the bottom if playing
4322 black or if the ICS flip flag is set, but let the user change
4323 it with the Flip View button. */
4324 flipView = appData.autoFlipView ?
4325 (newGameMode == IcsPlayingBlack) || ics_flip :
4328 /* Done with values from previous mode; copy in new ones */
4329 gameMode = newGameMode;
4331 ics_gamenum = gamenum;
4332 if (gamenum == gs_gamenum) {
4333 int klen = strlen(gs_kind);
4334 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4335 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4336 gameInfo.event = StrSave(str);
4338 gameInfo.event = StrSave("ICS game");
4340 gameInfo.site = StrSave(appData.icsHost);
4341 gameInfo.date = PGNDate();
4342 gameInfo.round = StrSave("-");
4343 gameInfo.white = StrSave(white);
4344 gameInfo.black = StrSave(black);
4345 timeControl = basetime * 60 * 1000;
4347 timeIncrement = increment * 1000;
4348 movesPerSession = 0;
4349 gameInfo.timeControl = TimeControlTagValue();
4350 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4351 if (appData.debugMode) {
4352 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4353 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4354 setbuf(debugFP, NULL);
4357 gameInfo.outOfBook = NULL;
4359 /* Do we have the ratings? */
4360 if (strcmp(player1Name, white) == 0 &&
4361 strcmp(player2Name, black) == 0) {
4362 if (appData.debugMode)
4363 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4364 player1Rating, player2Rating);
4365 gameInfo.whiteRating = player1Rating;
4366 gameInfo.blackRating = player2Rating;
4367 } else if (strcmp(player2Name, white) == 0 &&
4368 strcmp(player1Name, black) == 0) {
4369 if (appData.debugMode)
4370 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4371 player2Rating, player1Rating);
4372 gameInfo.whiteRating = player2Rating;
4373 gameInfo.blackRating = player1Rating;
4375 player1Name[0] = player2Name[0] = NULLCHAR;
4377 /* Silence shouts if requested */
4378 if (appData.quietPlay &&
4379 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4380 SendToICS(ics_prefix);
4381 SendToICS("set shout 0\n");
4385 /* Deal with midgame name changes */
4387 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4388 if (gameInfo.white) free(gameInfo.white);
4389 gameInfo.white = StrSave(white);
4391 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4392 if (gameInfo.black) free(gameInfo.black);
4393 gameInfo.black = StrSave(black);
4397 /* Throw away game result if anything actually changes in examine mode */
4398 if (gameMode == IcsExamining && !newGame) {
4399 gameInfo.result = GameUnfinished;
4400 if (gameInfo.resultDetails != NULL) {
4401 free(gameInfo.resultDetails);
4402 gameInfo.resultDetails = NULL;
4406 /* In pausing && IcsExamining mode, we ignore boards coming
4407 in if they are in a different variation than we are. */
4408 if (pauseExamInvalid) return;
4409 if (pausing && gameMode == IcsExamining) {
4410 if (moveNum <= pauseExamForwardMostMove) {
4411 pauseExamInvalid = TRUE;
4412 forwardMostMove = pauseExamForwardMostMove;
4417 if (appData.debugMode) {
4418 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4420 /* Parse the board */
4421 for (k = 0; k < ranks; k++) {
4422 for (j = 0; j < files; j++)
4423 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4424 if(gameInfo.holdingsWidth > 1) {
4425 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4426 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4429 CopyBoard(boards[moveNum], board);
4430 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4432 startedFromSetupPosition =
4433 !CompareBoards(board, initialPosition);
4434 if(startedFromSetupPosition)
4435 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4438 /* [HGM] Set castling rights. Take the outermost Rooks,
4439 to make it also work for FRC opening positions. Note that board12
4440 is really defective for later FRC positions, as it has no way to
4441 indicate which Rook can castle if they are on the same side of King.
4442 For the initial position we grant rights to the outermost Rooks,
4443 and remember thos rights, and we then copy them on positions
4444 later in an FRC game. This means WB might not recognize castlings with
4445 Rooks that have moved back to their original position as illegal,
4446 but in ICS mode that is not its job anyway.
4448 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4449 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4451 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4452 if(board[0][i] == WhiteRook) j = i;
4453 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4455 if(board[0][i] == WhiteRook) j = i;
4456 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4458 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4459 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4460 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4461 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4462 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4465 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4466 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4467 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468 if(board[BOARD_HEIGHT-1][k] == bKing)
4469 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4470 if(gameInfo.variant == VariantTwoKings) {
4471 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4472 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4473 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4476 r = boards[moveNum][CASTLING][0] = initialRights[0];
4477 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4478 r = boards[moveNum][CASTLING][1] = initialRights[1];
4479 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4480 r = boards[moveNum][CASTLING][3] = initialRights[3];
4481 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4482 r = boards[moveNum][CASTLING][4] = initialRights[4];
4483 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4484 /* wildcastle kludge: always assume King has rights */
4485 r = boards[moveNum][CASTLING][2] = initialRights[2];
4486 r = boards[moveNum][CASTLING][5] = initialRights[5];
4488 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4489 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4492 if (ics_getting_history == H_GOT_REQ_HEADER ||
4493 ics_getting_history == H_GOT_UNREQ_HEADER) {
4494 /* This was an initial position from a move list, not
4495 the current position */
4499 /* Update currentMove and known move number limits */
4500 newMove = newGame || moveNum > forwardMostMove;
4503 forwardMostMove = backwardMostMove = currentMove = moveNum;
4504 if (gameMode == IcsExamining && moveNum == 0) {
4505 /* Workaround for ICS limitation: we are not told the wild
4506 type when starting to examine a game. But if we ask for
4507 the move list, the move list header will tell us */
4508 ics_getting_history = H_REQUESTED;
4509 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4512 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4513 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4515 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4516 /* [HGM] applied this also to an engine that is silently watching */
4517 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4518 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4519 gameInfo.variant == currentlyInitializedVariant) {
4520 takeback = forwardMostMove - moveNum;
4521 for (i = 0; i < takeback; i++) {
4522 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4523 SendToProgram("undo\n", &first);
4528 forwardMostMove = moveNum;
4529 if (!pausing || currentMove > forwardMostMove)
4530 currentMove = forwardMostMove;
4532 /* New part of history that is not contiguous with old part */
4533 if (pausing && gameMode == IcsExamining) {
4534 pauseExamInvalid = TRUE;
4535 forwardMostMove = pauseExamForwardMostMove;
4538 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4540 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4541 // [HGM] when we will receive the move list we now request, it will be
4542 // fed to the engine from the first move on. So if the engine is not
4543 // in the initial position now, bring it there.
4544 InitChessProgram(&first, 0);
4547 ics_getting_history = H_REQUESTED;
4548 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4551 forwardMostMove = backwardMostMove = currentMove = moveNum;
4554 /* Update the clocks */
4555 if (strchr(elapsed_time, '.')) {
4557 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4558 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4560 /* Time is in seconds */
4561 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4562 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4567 if (appData.zippyPlay && newGame &&
4568 gameMode != IcsObserving && gameMode != IcsIdle &&
4569 gameMode != IcsExamining)
4570 ZippyFirstBoard(moveNum, basetime, increment);
4573 /* Put the move on the move list, first converting
4574 to canonical algebraic form. */
4576 if (appData.debugMode) {
4577 if (appData.debugMode) { int f = forwardMostMove;
4578 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4579 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4580 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4582 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4583 fprintf(debugFP, "moveNum = %d\n", moveNum);
4584 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4585 setbuf(debugFP, NULL);
4587 if (moveNum <= backwardMostMove) {
4588 /* We don't know what the board looked like before
4590 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4591 strcat(parseList[moveNum - 1], " ");
4592 strcat(parseList[moveNum - 1], elapsed_time);
4593 moveList[moveNum - 1][0] = NULLCHAR;
4594 } else if (strcmp(move_str, "none") == 0) {
4595 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4596 /* Again, we don't know what the board looked like;
4597 this is really the start of the game. */
4598 parseList[moveNum - 1][0] = NULLCHAR;
4599 moveList[moveNum - 1][0] = NULLCHAR;
4600 backwardMostMove = moveNum;
4601 startedFromSetupPosition = TRUE;
4602 fromX = fromY = toX = toY = -1;
4604 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4605 // So we parse the long-algebraic move string in stead of the SAN move
4606 int valid; char buf[MSG_SIZ], *prom;
4608 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4609 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4610 // str looks something like "Q/a1-a2"; kill the slash
4612 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4613 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4614 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4615 strcat(buf, prom); // long move lacks promo specification!
4616 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4617 if(appData.debugMode)
4618 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4619 safeStrCpy(move_str, buf, MSG_SIZ);
4621 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4622 &fromX, &fromY, &toX, &toY, &promoChar)
4623 || ParseOneMove(buf, moveNum - 1, &moveType,
4624 &fromX, &fromY, &toX, &toY, &promoChar);
4625 // end of long SAN patch
4627 (void) CoordsToAlgebraic(boards[moveNum - 1],
4628 PosFlags(moveNum - 1),
4629 fromY, fromX, toY, toX, promoChar,
4630 parseList[moveNum-1]);
4631 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4637 if(gameInfo.variant != VariantShogi)
4638 strcat(parseList[moveNum - 1], "+");
4641 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4642 strcat(parseList[moveNum - 1], "#");
4645 strcat(parseList[moveNum - 1], " ");
4646 strcat(parseList[moveNum - 1], elapsed_time);
4647 /* currentMoveString is set as a side-effect of ParseOneMove */
4648 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4649 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4650 strcat(moveList[moveNum - 1], "\n");
4652 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4653 && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4654 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4655 ChessSquare old, new = boards[moveNum][k][j];
4656 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4657 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4658 if(old == new) continue;
4659 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4660 else if(new == WhiteWazir || new == BlackWazir) {
4661 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4662 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4663 else boards[moveNum][k][j] = old; // preserve type of Gold
4664 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4665 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4668 /* Move from ICS was illegal!? Punt. */
4669 if (appData.debugMode) {
4670 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4671 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4673 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4674 strcat(parseList[moveNum - 1], " ");
4675 strcat(parseList[moveNum - 1], elapsed_time);
4676 moveList[moveNum - 1][0] = NULLCHAR;
4677 fromX = fromY = toX = toY = -1;
4680 if (appData.debugMode) {
4681 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4682 setbuf(debugFP, NULL);
4686 /* Send move to chess program (BEFORE animating it). */
4687 if (appData.zippyPlay && !newGame && newMove &&
4688 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4690 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4691 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4692 if (moveList[moveNum - 1][0] == NULLCHAR) {
4693 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4695 DisplayError(str, 0);
4697 if (first.sendTime) {
4698 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4700 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4701 if (firstMove && !bookHit) {
4703 if (first.useColors) {
4704 SendToProgram(gameMode == IcsPlayingWhite ?
4706 "black\ngo\n", &first);
4708 SendToProgram("go\n", &first);
4710 first.maybeThinking = TRUE;
4713 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4714 if (moveList[moveNum - 1][0] == NULLCHAR) {
4715 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4716 DisplayError(str, 0);
4718 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4719 SendMoveToProgram(moveNum - 1, &first);
4726 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4727 /* If move comes from a remote source, animate it. If it
4728 isn't remote, it will have already been animated. */
4729 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4730 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4732 if (!pausing && appData.highlightLastMove) {
4733 SetHighlights(fromX, fromY, toX, toY);
4737 /* Start the clocks */
4738 whiteFlag = blackFlag = FALSE;
4739 appData.clockMode = !(basetime == 0 && increment == 0);
4741 ics_clock_paused = TRUE;
4743 } else if (ticking == 1) {
4744 ics_clock_paused = FALSE;
4746 if (gameMode == IcsIdle ||
4747 relation == RELATION_OBSERVING_STATIC ||
4748 relation == RELATION_EXAMINING ||
4750 DisplayBothClocks();
4754 /* Display opponents and material strengths */
4755 if (gameInfo.variant != VariantBughouse &&
4756 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4757 if (tinyLayout || smallLayout) {
4758 if(gameInfo.variant == VariantNormal)
4759 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4760 gameInfo.white, white_stren, gameInfo.black, black_stren,
4761 basetime, increment);
4763 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4764 gameInfo.white, white_stren, gameInfo.black, black_stren,
4765 basetime, increment, (int) gameInfo.variant);
4767 if(gameInfo.variant == VariantNormal)
4768 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4769 gameInfo.white, white_stren, gameInfo.black, black_stren,
4770 basetime, increment);
4772 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4773 gameInfo.white, white_stren, gameInfo.black, black_stren,
4774 basetime, increment, VariantName(gameInfo.variant));
4777 if (appData.debugMode) {
4778 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4783 /* Display the board */
4784 if (!pausing && !appData.noGUI) {
4786 if (appData.premove)
4788 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4789 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4790 ClearPremoveHighlights();
4792 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4793 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4794 DrawPosition(j, boards[currentMove]);
4796 DisplayMove(moveNum - 1);
4797 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4798 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4799 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4800 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4804 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4806 if(bookHit) { // [HGM] book: simulate book reply
4807 static char bookMove[MSG_SIZ]; // a bit generous?
4809 programStats.nodes = programStats.depth = programStats.time =
4810 programStats.score = programStats.got_only_move = 0;
4811 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4813 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4814 strcat(bookMove, bookHit);
4815 HandleMachineMove(bookMove, &first);
4824 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4825 ics_getting_history = H_REQUESTED;
4826 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4832 AnalysisPeriodicEvent(force)
4835 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4836 && !force) || !appData.periodicUpdates)
4839 /* Send . command to Crafty to collect stats */
4840 SendToProgram(".\n", &first);
4842 /* Don't send another until we get a response (this makes
4843 us stop sending to old Crafty's which don't understand
4844 the "." command (sending illegal cmds resets node count & time,
4845 which looks bad)) */
4846 programStats.ok_to_send = 0;
4849 void ics_update_width(new_width)
4852 ics_printf("set width %d\n", new_width);
4856 SendMoveToProgram(moveNum, cps)
4858 ChessProgramState *cps;
4862 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4863 // null move in variant where engine does not understand it (for analysis purposes)
4864 SendBoard(cps, moveNum + 1); // send position after move in stead.
4867 if (cps->useUsermove) {
4868 SendToProgram("usermove ", cps);
4872 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4873 int len = space - parseList[moveNum];
4874 memcpy(buf, parseList[moveNum], len);
4876 buf[len] = NULLCHAR;
4878 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4880 SendToProgram(buf, cps);
4882 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4883 AlphaRank(moveList[moveNum], 4);
4884 SendToProgram(moveList[moveNum], cps);
4885 AlphaRank(moveList[moveNum], 4); // and back
4887 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4888 * the engine. It would be nice to have a better way to identify castle
4890 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4891 && cps->useOOCastle) {
4892 int fromX = moveList[moveNum][0] - AAA;
4893 int fromY = moveList[moveNum][1] - ONE;
4894 int toX = moveList[moveNum][2] - AAA;
4895 int toY = moveList[moveNum][3] - ONE;
4896 if((boards[moveNum][fromY][fromX] == WhiteKing
4897 && boards[moveNum][toY][toX] == WhiteRook)
4898 || (boards[moveNum][fromY][fromX] == BlackKing
4899 && boards[moveNum][toY][toX] == BlackRook)) {
4900 if(toX > fromX) SendToProgram("O-O\n", cps);
4901 else SendToProgram("O-O-O\n", cps);
4903 else SendToProgram(moveList[moveNum], cps);
4905 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4906 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4907 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4908 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4909 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4911 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4912 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4913 SendToProgram(buf, cps);
4915 else SendToProgram(moveList[moveNum], cps);
4916 /* End of additions by Tord */
4919 /* [HGM] setting up the opening has brought engine in force mode! */
4920 /* Send 'go' if we are in a mode where machine should play. */
4921 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4922 (gameMode == TwoMachinesPlay ||
4924 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4926 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4927 SendToProgram("go\n", cps);
4928 if (appData.debugMode) {
4929 fprintf(debugFP, "(extra)\n");
4932 setboardSpoiledMachineBlack = 0;
4936 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4938 int fromX, fromY, toX, toY;
4941 char user_move[MSG_SIZ];
4945 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4946 (int)moveType, fromX, fromY, toX, toY);
4947 DisplayError(user_move + strlen("say "), 0);
4949 case WhiteKingSideCastle:
4950 case BlackKingSideCastle:
4951 case WhiteQueenSideCastleWild:
4952 case BlackQueenSideCastleWild:
4954 case WhiteHSideCastleFR:
4955 case BlackHSideCastleFR:
4957 snprintf(user_move, MSG_SIZ, "o-o\n");
4959 case WhiteQueenSideCastle:
4960 case BlackQueenSideCastle:
4961 case WhiteKingSideCastleWild:
4962 case BlackKingSideCastleWild:
4964 case WhiteASideCastleFR:
4965 case BlackASideCastleFR:
4967 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4969 case WhiteNonPromotion:
4970 case BlackNonPromotion:
4971 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4973 case WhitePromotion:
4974 case BlackPromotion:
4975 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4976 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4977 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4978 PieceToChar(WhiteFerz));
4979 else if(gameInfo.variant == VariantGreat)
4980 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4981 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4982 PieceToChar(WhiteMan));
4984 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4985 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4991 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4992 ToUpper(PieceToChar((ChessSquare) fromX)),
4993 AAA + toX, ONE + toY);
4995 case IllegalMove: /* could be a variant we don't quite understand */
4996 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4998 case WhiteCapturesEnPassant:
4999 case BlackCapturesEnPassant:
5000 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5001 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5004 SendToICS(user_move);
5005 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5006 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5011 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5012 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5013 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5014 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5015 DisplayError("You cannot do this while you are playing or observing", 0);
5018 if(gameMode != IcsExamining) { // is this ever not the case?
5019 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5021 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5022 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5023 } else { // on FICS we must first go to general examine mode
5024 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5026 if(gameInfo.variant != VariantNormal) {
5027 // try figure out wild number, as xboard names are not always valid on ICS
5028 for(i=1; i<=36; i++) {
5029 snprintf(buf, MSG_SIZ, "wild/%d", i);
5030 if(StringToVariant(buf) == gameInfo.variant) break;
5032 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5033 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5034 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5035 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5036 SendToICS(ics_prefix);
5038 if(startedFromSetupPosition || backwardMostMove != 0) {
5039 fen = PositionToFEN(backwardMostMove, NULL);
5040 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5041 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5043 } else { // FICS: everything has to set by separate bsetup commands
5044 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5045 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5047 if(!WhiteOnMove(backwardMostMove)) {
5048 SendToICS("bsetup tomove black\n");
5050 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5051 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5053 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5054 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5056 i = boards[backwardMostMove][EP_STATUS];
5057 if(i >= 0) { // set e.p.
5058 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5064 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5065 SendToICS("bsetup done\n"); // switch to normal examining.
5067 for(i = backwardMostMove; i<last; i++) {
5069 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5072 SendToICS(ics_prefix);
5073 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5077 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5082 if (rf == DROP_RANK) {
5083 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5084 sprintf(move, "%c@%c%c\n",
5085 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5087 if (promoChar == 'x' || promoChar == NULLCHAR) {
5088 sprintf(move, "%c%c%c%c\n",
5089 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5091 sprintf(move, "%c%c%c%c%c\n",
5092 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5098 ProcessICSInitScript(f)
5103 while (fgets(buf, MSG_SIZ, f)) {
5104 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5111 static int lastX, lastY, selectFlag, dragging;
5116 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5117 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5118 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5119 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5120 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5121 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5124 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5125 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5126 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5127 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5129 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5130 appData.testLegality && (promoSweep == king ||
5131 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5132 ChangeDragPiece(promoSweep);
5135 int PromoScroll(int x, int y)
5139 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5140 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5141 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5142 if(!step) return FALSE;
5143 lastX = x; lastY = y;
5144 if((promoSweep < BlackPawn) == flipView) step = -step;
5145 if(step > 0) selectFlag = 1;
5146 if(!selectFlag) Sweep(step);
5153 ChessSquare piece = boards[currentMove][toY][toX];
5156 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5157 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5158 if(!step) step = -1;
5159 } while(PieceToChar(pieceSweep) == '.');
5160 boards[currentMove][toY][toX] = pieceSweep;
5161 DrawPosition(FALSE, boards[currentMove]);
5162 boards[currentMove][toY][toX] = piece;
5164 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5166 AlphaRank(char *move, int n)
5168 // char *p = move, c; int x, y;
5170 if (appData.debugMode) {
5171 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5175 move[2]>='0' && move[2]<='9' &&
5176 move[3]>='a' && move[3]<='x' ) {
5178 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5179 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5181 if(move[0]>='0' && move[0]<='9' &&
5182 move[1]>='a' && move[1]<='x' &&
5183 move[2]>='0' && move[2]<='9' &&
5184 move[3]>='a' && move[3]<='x' ) {
5185 /* input move, Shogi -> normal */
5186 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5187 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5188 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5189 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192 move[3]>='0' && move[3]<='9' &&
5193 move[2]>='a' && move[2]<='x' ) {
5195 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5196 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199 move[0]>='a' && move[0]<='x' &&
5200 move[3]>='0' && move[3]<='9' &&
5201 move[2]>='a' && move[2]<='x' ) {
5202 /* output move, normal -> Shogi */
5203 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5204 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5205 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5206 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5207 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5209 if (appData.debugMode) {
5210 fprintf(debugFP, " out = '%s'\n", move);
5214 char yy_textstr[8000];
5216 /* Parser for moves from gnuchess, ICS, or user typein box */
5218 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5221 ChessMove *moveType;
5222 int *fromX, *fromY, *toX, *toY;
5225 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5227 switch (*moveType) {
5228 case WhitePromotion:
5229 case BlackPromotion:
5230 case WhiteNonPromotion:
5231 case BlackNonPromotion:
5233 case WhiteCapturesEnPassant:
5234 case BlackCapturesEnPassant:
5235 case WhiteKingSideCastle:
5236 case WhiteQueenSideCastle:
5237 case BlackKingSideCastle:
5238 case BlackQueenSideCastle:
5239 case WhiteKingSideCastleWild:
5240 case WhiteQueenSideCastleWild:
5241 case BlackKingSideCastleWild:
5242 case BlackQueenSideCastleWild:
5243 /* Code added by Tord: */
5244 case WhiteHSideCastleFR:
5245 case WhiteASideCastleFR:
5246 case BlackHSideCastleFR:
5247 case BlackASideCastleFR:
5248 /* End of code added by Tord */
5249 case IllegalMove: /* bug or odd chess variant */
5250 *fromX = currentMoveString[0] - AAA;
5251 *fromY = currentMoveString[1] - ONE;
5252 *toX = currentMoveString[2] - AAA;
5253 *toY = currentMoveString[3] - ONE;
5254 *promoChar = currentMoveString[4];
5255 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5256 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5257 if (appData.debugMode) {
5258 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5260 *fromX = *fromY = *toX = *toY = 0;
5263 if (appData.testLegality) {
5264 return (*moveType != IllegalMove);
5266 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5267 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5272 *fromX = *moveType == WhiteDrop ?
5273 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5274 (int) CharToPiece(ToLower(currentMoveString[0]));
5276 *toX = currentMoveString[2] - AAA;
5277 *toY = currentMoveString[3] - ONE;
5278 *promoChar = NULLCHAR;
5282 case ImpossibleMove:
5292 if (appData.debugMode) {
5293 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5296 *fromX = *fromY = *toX = *toY = 0;
5297 *promoChar = NULLCHAR;
5302 Boolean pushed = FALSE;
5303 char *lastParseAttempt;
5306 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5307 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5308 int fromX, fromY, toX, toY; char promoChar;
5313 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5314 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5317 endPV = forwardMostMove;
5319 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5320 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5321 lastParseAttempt = pv;
5322 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5323 if(appData.debugMode){
5324 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);
5326 if(!valid && nr == 0 &&
5327 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5328 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5329 // Hande case where played move is different from leading PV move
5330 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5331 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5332 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5333 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5334 endPV += 2; // if position different, keep this
5335 moveList[endPV-1][0] = fromX + AAA;
5336 moveList[endPV-1][1] = fromY + ONE;
5337 moveList[endPV-1][2] = toX + AAA;
5338 moveList[endPV-1][3] = toY + ONE;
5339 parseList[endPV-1][0] = NULLCHAR;
5340 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5343 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5344 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5345 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5346 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5347 valid++; // allow comments in PV
5351 if(endPV+1 > framePtr) break; // no space, truncate
5354 CopyBoard(boards[endPV], boards[endPV-1]);
5355 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5356 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5357 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5358 CoordsToAlgebraic(boards[endPV - 1],
5359 PosFlags(endPV - 1),
5360 fromY, fromX, toY, toX, promoChar,
5361 parseList[endPV - 1]);
5363 if(atEnd == 2) return; // used hidden, for PV conversion
5364 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5365 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5366 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5367 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5368 DrawPosition(TRUE, boards[currentMove]);
5372 MultiPV(ChessProgramState *cps)
5373 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5375 for(i=0; i<cps->nrOptions; i++)
5376 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5382 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5384 int startPV, multi, lineStart, origIndex = index;
5385 char *p, buf2[MSG_SIZ];
5387 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5388 lastX = x; lastY = y;
5389 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5390 lineStart = startPV = index;
5391 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5392 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5394 do{ while(buf[index] && buf[index] != '\n') index++;
5395 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5397 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5398 int n = first.option[multi].value;
5399 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5400 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5401 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5402 first.option[multi].value = n;
5406 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5407 *start = startPV; *end = index-1;
5414 static char buf[10*MSG_SIZ];
5415 int i, k=0, savedEnd=endPV;
5417 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5418 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5419 for(i = forwardMostMove; i<endPV; i++){
5420 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5421 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5424 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5425 if(forwardMostMove < savedEnd) PopInner(0);
5431 LoadPV(int x, int y)
5432 { // called on right mouse click to load PV
5433 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5434 lastX = x; lastY = y;
5435 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5442 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5443 if(endPV < 0) return;
5445 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5446 Boolean saveAnimate = appData.animate;
5448 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5449 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5450 } else storedGames--; // abandon shelved tail of original game
5453 forwardMostMove = currentMove;
5454 currentMove = oldFMM;
5455 appData.animate = FALSE;
5456 ToNrEvent(forwardMostMove);
5457 appData.animate = saveAnimate;
5459 currentMove = forwardMostMove;
5460 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5461 ClearPremoveHighlights();
5462 DrawPosition(TRUE, boards[currentMove]);
5466 MovePV(int x, int y, int h)
5467 { // step through PV based on mouse coordinates (called on mouse move)
5468 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5470 // we must somehow check if right button is still down (might be released off board!)
5471 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5472 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5473 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5475 lastX = x; lastY = y;
5477 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5478 if(endPV < 0) return;
5479 if(y < margin) step = 1; else
5480 if(y > h - margin) step = -1;
5481 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5482 currentMove += step;
5483 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5484 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5485 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5486 DrawPosition(FALSE, boards[currentMove]);
5490 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5491 // All positions will have equal probability, but the current method will not provide a unique
5492 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5498 int piecesLeft[(int)BlackPawn];
5499 int seed, nrOfShuffles;
5501 void GetPositionNumber()
5502 { // sets global variable seed
5505 seed = appData.defaultFrcPosition;
5506 if(seed < 0) { // randomize based on time for negative FRC position numbers
5507 for(i=0; i<50; i++) seed += random();
5508 seed = random() ^ random() >> 8 ^ random() << 8;
5509 if(seed<0) seed = -seed;
5513 int put(Board board, int pieceType, int rank, int n, int shade)
5514 // put the piece on the (n-1)-th empty squares of the given shade
5518 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5519 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5520 board[rank][i] = (ChessSquare) pieceType;
5521 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5523 piecesLeft[pieceType]--;
5531 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5532 // calculate where the next piece goes, (any empty square), and put it there
5536 i = seed % squaresLeft[shade];
5537 nrOfShuffles *= squaresLeft[shade];
5538 seed /= squaresLeft[shade];
5539 put(board, pieceType, rank, i, shade);
5542 void AddTwoPieces(Board board, int pieceType, int rank)
5543 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5545 int i, n=squaresLeft[ANY], j=n-1, k;
5547 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5548 i = seed % k; // pick one
5551 while(i >= j) i -= j--;
5552 j = n - 1 - j; i += j;
5553 put(board, pieceType, rank, j, ANY);
5554 put(board, pieceType, rank, i, ANY);
5557 void SetUpShuffle(Board board, int number)
5561 GetPositionNumber(); nrOfShuffles = 1;
5563 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5564 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5565 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5567 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5569 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5570 p = (int) board[0][i];
5571 if(p < (int) BlackPawn) piecesLeft[p] ++;
5572 board[0][i] = EmptySquare;
5575 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5576 // shuffles restricted to allow normal castling put KRR first
5577 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5578 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5579 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5580 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5581 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5582 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5583 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5584 put(board, WhiteRook, 0, 0, ANY);
5585 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5588 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5589 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5590 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5591 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5592 while(piecesLeft[p] >= 2) {
5593 AddOnePiece(board, p, 0, LITE);
5594 AddOnePiece(board, p, 0, DARK);
5596 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5599 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5600 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5601 // but we leave King and Rooks for last, to possibly obey FRC restriction
5602 if(p == (int)WhiteRook) continue;
5603 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5604 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5607 // now everything is placed, except perhaps King (Unicorn) and Rooks
5609 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5610 // Last King gets castling rights
5611 while(piecesLeft[(int)WhiteUnicorn]) {
5612 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5613 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5616 while(piecesLeft[(int)WhiteKing]) {
5617 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5618 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5623 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5624 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5627 // Only Rooks can be left; simply place them all
5628 while(piecesLeft[(int)WhiteRook]) {
5629 i = put(board, WhiteRook, 0, 0, ANY);
5630 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5633 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5635 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5638 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5639 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5642 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5645 int SetCharTable( char *table, const char * map )
5646 /* [HGM] moved here from winboard.c because of its general usefulness */
5647 /* Basically a safe strcpy that uses the last character as King */
5649 int result = FALSE; int NrPieces;
5651 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5652 && NrPieces >= 12 && !(NrPieces&1)) {
5653 int i; /* [HGM] Accept even length from 12 to 34 */
5655 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5656 for( i=0; i<NrPieces/2-1; i++ ) {
5658 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5660 table[(int) WhiteKing] = map[NrPieces/2-1];
5661 table[(int) BlackKing] = map[NrPieces-1];
5669 void Prelude(Board board)
5670 { // [HGM] superchess: random selection of exo-pieces
5671 int i, j, k; ChessSquare p;
5672 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5674 GetPositionNumber(); // use FRC position number
5676 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5677 SetCharTable(pieceToChar, appData.pieceToCharTable);
5678 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5679 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5682 j = seed%4; seed /= 4;
5683 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5684 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5685 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5686 j = seed%3 + (seed%3 >= j); seed /= 3;
5687 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5688 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5689 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5690 j = seed%3; seed /= 3;
5691 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5692 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5693 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5694 j = seed%2 + (seed%2 >= j); seed /= 2;
5695 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5696 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5697 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5698 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5699 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5700 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5701 put(board, exoPieces[0], 0, 0, ANY);
5702 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5706 InitPosition(redraw)
5709 ChessSquare (* pieces)[BOARD_FILES];
5710 int i, j, pawnRow, overrule,
5711 oldx = gameInfo.boardWidth,
5712 oldy = gameInfo.boardHeight,
5713 oldh = gameInfo.holdingsWidth;
5716 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5718 /* [AS] Initialize pv info list [HGM] and game status */
5720 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5721 pvInfoList[i].depth = 0;
5722 boards[i][EP_STATUS] = EP_NONE;
5723 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5726 initialRulePlies = 0; /* 50-move counter start */
5728 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5729 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5733 /* [HGM] logic here is completely changed. In stead of full positions */
5734 /* the initialized data only consist of the two backranks. The switch */
5735 /* selects which one we will use, which is than copied to the Board */
5736 /* initialPosition, which for the rest is initialized by Pawns and */
5737 /* empty squares. This initial position is then copied to boards[0], */
5738 /* possibly after shuffling, so that it remains available. */
5740 gameInfo.holdingsWidth = 0; /* default board sizes */
5741 gameInfo.boardWidth = 8;
5742 gameInfo.boardHeight = 8;
5743 gameInfo.holdingsSize = 0;
5744 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5745 for(i=0; i<BOARD_FILES-2; i++)
5746 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5747 initialPosition[EP_STATUS] = EP_NONE;
5748 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5749 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5750 SetCharTable(pieceNickName, appData.pieceNickNames);
5751 else SetCharTable(pieceNickName, "............");
5754 switch (gameInfo.variant) {
5755 case VariantFischeRandom:
5756 shuffleOpenings = TRUE;
5759 case VariantShatranj:
5760 pieces = ShatranjArray;
5761 nrCastlingRights = 0;
5762 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5765 pieces = makrukArray;
5766 nrCastlingRights = 0;
5767 startedFromSetupPosition = TRUE;
5768 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5770 case VariantTwoKings:
5771 pieces = twoKingsArray;
5774 pieces = GrandArray;
5775 nrCastlingRights = 0;
5776 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5777 gameInfo.boardWidth = 10;
5778 gameInfo.boardHeight = 10;
5779 gameInfo.holdingsSize = 7;
5781 case VariantCapaRandom:
5782 shuffleOpenings = TRUE;
5783 case VariantCapablanca:
5784 pieces = CapablancaArray;
5785 gameInfo.boardWidth = 10;
5786 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789 pieces = GothicArray;
5790 gameInfo.boardWidth = 10;
5791 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5795 gameInfo.holdingsSize = 7;
5798 pieces = JanusArray;
5799 gameInfo.boardWidth = 10;
5800 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5801 nrCastlingRights = 6;
5802 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5803 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5804 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5805 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5806 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5807 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5810 pieces = FalconArray;
5811 gameInfo.boardWidth = 10;
5812 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5814 case VariantXiangqi:
5815 pieces = XiangqiArray;
5816 gameInfo.boardWidth = 9;
5817 gameInfo.boardHeight = 10;
5818 nrCastlingRights = 0;
5819 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5822 pieces = ShogiArray;
5823 gameInfo.boardWidth = 9;
5824 gameInfo.boardHeight = 9;
5825 gameInfo.holdingsSize = 7;
5826 nrCastlingRights = 0;
5827 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5829 case VariantCourier:
5830 pieces = CourierArray;
5831 gameInfo.boardWidth = 12;
5832 nrCastlingRights = 0;
5833 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5835 case VariantKnightmate:
5836 pieces = KnightmateArray;
5837 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5839 case VariantSpartan:
5840 pieces = SpartanArray;
5841 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5844 pieces = fairyArray;
5845 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5848 pieces = GreatArray;
5849 gameInfo.boardWidth = 10;
5850 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5851 gameInfo.holdingsSize = 8;
5855 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5856 gameInfo.holdingsSize = 8;
5857 startedFromSetupPosition = TRUE;
5859 case VariantCrazyhouse:
5860 case VariantBughouse:
5862 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5863 gameInfo.holdingsSize = 5;
5865 case VariantWildCastle:
5867 /* !!?shuffle with kings guaranteed to be on d or e file */
5868 shuffleOpenings = 1;
5870 case VariantNoCastle:
5872 nrCastlingRights = 0;
5873 /* !!?unconstrained back-rank shuffle */
5874 shuffleOpenings = 1;
5879 if(appData.NrFiles >= 0) {
5880 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5881 gameInfo.boardWidth = appData.NrFiles;
5883 if(appData.NrRanks >= 0) {
5884 gameInfo.boardHeight = appData.NrRanks;
5886 if(appData.holdingsSize >= 0) {
5887 i = appData.holdingsSize;
5888 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5889 gameInfo.holdingsSize = i;
5891 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5892 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5893 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5895 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5896 if(pawnRow < 1) pawnRow = 1;
5897 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5899 /* User pieceToChar list overrules defaults */
5900 if(appData.pieceToCharTable != NULL)
5901 SetCharTable(pieceToChar, appData.pieceToCharTable);
5903 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5905 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5906 s = (ChessSquare) 0; /* account holding counts in guard band */
5907 for( i=0; i<BOARD_HEIGHT; i++ )
5908 initialPosition[i][j] = s;
5910 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5911 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5912 initialPosition[pawnRow][j] = WhitePawn;
5913 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5914 if(gameInfo.variant == VariantXiangqi) {
5916 initialPosition[pawnRow][j] =
5917 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5918 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5919 initialPosition[2][j] = WhiteCannon;
5920 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5924 if(gameInfo.variant == VariantGrand) {
5925 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5926 initialPosition[0][j] = WhiteRook;
5927 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5930 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5932 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5935 initialPosition[1][j] = WhiteBishop;
5936 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5938 initialPosition[1][j] = WhiteRook;
5939 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5942 if( nrCastlingRights == -1) {
5943 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5944 /* This sets default castling rights from none to normal corners */
5945 /* Variants with other castling rights must set them themselves above */
5946 nrCastlingRights = 6;
5948 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5949 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5950 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5951 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5952 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5953 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5956 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5957 if(gameInfo.variant == VariantGreat) { // promotion commoners
5958 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5959 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5960 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5961 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5963 if( gameInfo.variant == VariantSChess ) {
5964 initialPosition[1][0] = BlackMarshall;
5965 initialPosition[2][0] = BlackAngel;
5966 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5967 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5968 initialPosition[1][1] = initialPosition[2][1] =
5969 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5971 if (appData.debugMode) {
5972 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5974 if(shuffleOpenings) {
5975 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5976 startedFromSetupPosition = TRUE;
5978 if(startedFromPositionFile) {
5979 /* [HGM] loadPos: use PositionFile for every new game */
5980 CopyBoard(initialPosition, filePosition);
5981 for(i=0; i<nrCastlingRights; i++)
5982 initialRights[i] = filePosition[CASTLING][i];
5983 startedFromSetupPosition = TRUE;
5986 CopyBoard(boards[0], initialPosition);
5988 if(oldx != gameInfo.boardWidth ||
5989 oldy != gameInfo.boardHeight ||
5990 oldv != gameInfo.variant ||
5991 oldh != gameInfo.holdingsWidth
5993 InitDrawingSizes(-2 ,0);
5995 oldv = gameInfo.variant;
5997 DrawPosition(TRUE, boards[currentMove]);
6001 SendBoard(cps, moveNum)
6002 ChessProgramState *cps;
6005 char message[MSG_SIZ];
6007 if (cps->useSetboard) {
6008 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6009 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6010 SendToProgram(message, cps);
6016 /* Kludge to set black to move, avoiding the troublesome and now
6017 * deprecated "black" command.
6019 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6020 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6022 SendToProgram("edit\n", cps);
6023 SendToProgram("#\n", cps);
6024 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6025 bp = &boards[moveNum][i][BOARD_LEFT];
6026 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6027 if ((int) *bp < (int) BlackPawn) {
6028 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6030 if(message[0] == '+' || message[0] == '~') {
6031 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6032 PieceToChar((ChessSquare)(DEMOTED *bp)),
6035 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6036 message[1] = BOARD_RGHT - 1 - j + '1';
6037 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6039 SendToProgram(message, cps);
6044 SendToProgram("c\n", cps);
6045 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6046 bp = &boards[moveNum][i][BOARD_LEFT];
6047 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6048 if (((int) *bp != (int) EmptySquare)
6049 && ((int) *bp >= (int) BlackPawn)) {
6050 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6052 if(message[0] == '+' || message[0] == '~') {
6053 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6054 PieceToChar((ChessSquare)(DEMOTED *bp)),
6057 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6058 message[1] = BOARD_RGHT - 1 - j + '1';
6059 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6061 SendToProgram(message, cps);
6066 SendToProgram(".\n", cps);
6068 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6072 DefaultPromoChoice(int white)
6075 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6076 result = WhiteFerz; // no choice
6077 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6078 result= WhiteKing; // in Suicide Q is the last thing we want
6079 else if(gameInfo.variant == VariantSpartan)
6080 result = white ? WhiteQueen : WhiteAngel;
6081 else result = WhiteQueen;
6082 if(!white) result = WHITE_TO_BLACK result;
6086 static int autoQueen; // [HGM] oneclick
6089 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6091 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6092 /* [HGM] add Shogi promotions */
6093 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6098 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6099 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6101 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6102 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6105 piece = boards[currentMove][fromY][fromX];
6106 if(gameInfo.variant == VariantShogi) {
6107 promotionZoneSize = BOARD_HEIGHT/3;
6108 highestPromotingPiece = (int)WhiteFerz;
6109 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6110 promotionZoneSize = 3;
6113 // Treat Lance as Pawn when it is not representing Amazon
6114 if(gameInfo.variant != VariantSuper) {
6115 if(piece == WhiteLance) piece = WhitePawn; else
6116 if(piece == BlackLance) piece = BlackPawn;
6119 // next weed out all moves that do not touch the promotion zone at all
6120 if((int)piece >= BlackPawn) {
6121 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6123 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6125 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6126 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6129 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6131 // weed out mandatory Shogi promotions
6132 if(gameInfo.variant == VariantShogi) {
6133 if(piece >= BlackPawn) {
6134 if(toY == 0 && piece == BlackPawn ||
6135 toY == 0 && piece == BlackQueen ||
6136 toY <= 1 && piece == BlackKnight) {
6141 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6142 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6143 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6150 // weed out obviously illegal Pawn moves
6151 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6152 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6153 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6154 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6155 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6156 // note we are not allowed to test for valid (non-)capture, due to premove
6159 // we either have a choice what to promote to, or (in Shogi) whether to promote
6160 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6161 *promoChoice = PieceToChar(BlackFerz); // no choice
6164 // no sense asking what we must promote to if it is going to explode...
6165 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6166 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6169 // give caller the default choice even if we will not make it
6170 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6171 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6172 if( sweepSelect && gameInfo.variant != VariantGreat
6173 && gameInfo.variant != VariantGrand
6174 && gameInfo.variant != VariantSuper) return FALSE;
6175 if(autoQueen) return FALSE; // predetermined
6177 // suppress promotion popup on illegal moves that are not premoves
6178 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6179 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6180 if(appData.testLegality && !premove) {
6181 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6182 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6183 if(moveType != WhitePromotion && moveType != BlackPromotion)
6191 InPalace(row, column)
6193 { /* [HGM] for Xiangqi */
6194 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6195 column < (BOARD_WIDTH + 4)/2 &&
6196 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6201 PieceForSquare (x, y)
6205 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6208 return boards[currentMove][y][x];
6212 OKToStartUserMove(x, y)
6215 ChessSquare from_piece;
6218 if (matchMode) return FALSE;
6219 if (gameMode == EditPosition) return TRUE;
6221 if (x >= 0 && y >= 0)
6222 from_piece = boards[currentMove][y][x];
6224 from_piece = EmptySquare;
6226 if (from_piece == EmptySquare) return FALSE;
6228 white_piece = (int)from_piece >= (int)WhitePawn &&
6229 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6233 case TwoMachinesPlay:
6241 case MachinePlaysWhite:
6242 case IcsPlayingBlack:
6243 if (appData.zippyPlay) return FALSE;
6245 DisplayMoveError(_("You are playing Black"));
6250 case MachinePlaysBlack:
6251 case IcsPlayingWhite:
6252 if (appData.zippyPlay) return FALSE;
6254 DisplayMoveError(_("You are playing White"));
6259 case PlayFromGameFile:
6260 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6262 if (!white_piece && WhiteOnMove(currentMove)) {
6263 DisplayMoveError(_("It is White's turn"));
6266 if (white_piece && !WhiteOnMove(currentMove)) {
6267 DisplayMoveError(_("It is Black's turn"));
6270 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6271 /* Editing correspondence game history */
6272 /* Could disallow this or prompt for confirmation */
6277 case BeginningOfGame:
6278 if (appData.icsActive) return FALSE;
6279 if (!appData.noChessProgram) {
6281 DisplayMoveError(_("You are playing White"));
6288 if (!white_piece && WhiteOnMove(currentMove)) {
6289 DisplayMoveError(_("It is White's turn"));
6292 if (white_piece && !WhiteOnMove(currentMove)) {
6293 DisplayMoveError(_("It is Black's turn"));
6302 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6303 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6304 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6305 && gameMode != AnalyzeFile && gameMode != Training) {
6306 DisplayMoveError(_("Displayed position is not current"));
6313 OnlyMove(int *x, int *y, Boolean captures) {
6314 DisambiguateClosure cl;
6315 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6317 case MachinePlaysBlack:
6318 case IcsPlayingWhite:
6319 case BeginningOfGame:
6320 if(!WhiteOnMove(currentMove)) return FALSE;
6322 case MachinePlaysWhite:
6323 case IcsPlayingBlack:
6324 if(WhiteOnMove(currentMove)) return FALSE;
6331 cl.pieceIn = EmptySquare;
6336 cl.promoCharIn = NULLCHAR;
6337 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6338 if( cl.kind == NormalMove ||
6339 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6340 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6341 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6348 if(cl.kind != ImpossibleMove) return FALSE;
6349 cl.pieceIn = EmptySquare;
6354 cl.promoCharIn = NULLCHAR;
6355 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356 if( cl.kind == NormalMove ||
6357 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6364 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6370 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6371 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6372 int lastLoadGameUseList = FALSE;
6373 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6374 ChessMove lastLoadGameStart = EndOfFile;
6377 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6378 int fromX, fromY, toX, toY;
6382 ChessSquare pdown, pup;
6384 /* Check if the user is playing in turn. This is complicated because we
6385 let the user "pick up" a piece before it is his turn. So the piece he
6386 tried to pick up may have been captured by the time he puts it down!
6387 Therefore we use the color the user is supposed to be playing in this
6388 test, not the color of the piece that is currently on the starting
6389 square---except in EditGame mode, where the user is playing both
6390 sides; fortunately there the capture race can't happen. (It can
6391 now happen in IcsExamining mode, but that's just too bad. The user
6392 will get a somewhat confusing message in that case.)
6397 case TwoMachinesPlay:
6401 /* We switched into a game mode where moves are not accepted,
6402 perhaps while the mouse button was down. */
6405 case MachinePlaysWhite:
6406 /* User is moving for Black */
6407 if (WhiteOnMove(currentMove)) {
6408 DisplayMoveError(_("It is White's turn"));
6413 case MachinePlaysBlack:
6414 /* User is moving for White */
6415 if (!WhiteOnMove(currentMove)) {
6416 DisplayMoveError(_("It is Black's turn"));
6421 case PlayFromGameFile:
6422 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6425 case BeginningOfGame:
6428 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6429 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6430 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6431 /* User is moving for Black */
6432 if (WhiteOnMove(currentMove)) {
6433 DisplayMoveError(_("It is White's turn"));
6437 /* User is moving for White */
6438 if (!WhiteOnMove(currentMove)) {
6439 DisplayMoveError(_("It is Black's turn"));
6445 case IcsPlayingBlack:
6446 /* User is moving for Black */
6447 if (WhiteOnMove(currentMove)) {
6448 if (!appData.premove) {
6449 DisplayMoveError(_("It is White's turn"));
6450 } else if (toX >= 0 && toY >= 0) {
6453 premoveFromX = fromX;
6454 premoveFromY = fromY;
6455 premovePromoChar = promoChar;
6457 if (appData.debugMode)
6458 fprintf(debugFP, "Got premove: fromX %d,"
6459 "fromY %d, toX %d, toY %d\n",
6460 fromX, fromY, toX, toY);
6466 case IcsPlayingWhite:
6467 /* User is moving for White */
6468 if (!WhiteOnMove(currentMove)) {
6469 if (!appData.premove) {
6470 DisplayMoveError(_("It is Black's turn"));
6471 } else if (toX >= 0 && toY >= 0) {
6474 premoveFromX = fromX;
6475 premoveFromY = fromY;
6476 premovePromoChar = promoChar;
6478 if (appData.debugMode)
6479 fprintf(debugFP, "Got premove: fromX %d,"
6480 "fromY %d, toX %d, toY %d\n",
6481 fromX, fromY, toX, toY);
6491 /* EditPosition, empty square, or different color piece;
6492 click-click move is possible */
6493 if (toX == -2 || toY == -2) {
6494 boards[0][fromY][fromX] = EmptySquare;
6495 DrawPosition(FALSE, boards[currentMove]);
6497 } else if (toX >= 0 && toY >= 0) {
6498 boards[0][toY][toX] = boards[0][fromY][fromX];
6499 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6500 if(boards[0][fromY][0] != EmptySquare) {
6501 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6502 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6505 if(fromX == BOARD_RGHT+1) {
6506 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6507 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6508 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6511 boards[0][fromY][fromX] = EmptySquare;
6512 DrawPosition(FALSE, boards[currentMove]);
6518 if(toX < 0 || toY < 0) return;
6519 pdown = boards[currentMove][fromY][fromX];
6520 pup = boards[currentMove][toY][toX];
6522 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6523 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6524 if( pup != EmptySquare ) return;
6525 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6526 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6527 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6528 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6529 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6530 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6531 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6535 /* [HGM] always test for legality, to get promotion info */
6536 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6537 fromY, fromX, toY, toX, promoChar);
6539 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6541 /* [HGM] but possibly ignore an IllegalMove result */
6542 if (appData.testLegality) {
6543 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6544 DisplayMoveError(_("Illegal move"));
6549 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6552 /* Common tail of UserMoveEvent and DropMenuEvent */
6554 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6556 int fromX, fromY, toX, toY;
6557 /*char*/int promoChar;
6561 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6562 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6563 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6564 if(WhiteOnMove(currentMove)) {
6565 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6567 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6571 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6572 move type in caller when we know the move is a legal promotion */
6573 if(moveType == NormalMove && promoChar)
6574 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6576 /* [HGM] <popupFix> The following if has been moved here from
6577 UserMoveEvent(). Because it seemed to belong here (why not allow
6578 piece drops in training games?), and because it can only be
6579 performed after it is known to what we promote. */
6580 if (gameMode == Training) {
6581 /* compare the move played on the board to the next move in the
6582 * game. If they match, display the move and the opponent's response.
6583 * If they don't match, display an error message.
6587 CopyBoard(testBoard, boards[currentMove]);
6588 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6590 if (CompareBoards(testBoard, boards[currentMove+1])) {
6591 ForwardInner(currentMove+1);
6593 /* Autoplay the opponent's response.
6594 * if appData.animate was TRUE when Training mode was entered,
6595 * the response will be animated.
6597 saveAnimate = appData.animate;
6598 appData.animate = animateTraining;
6599 ForwardInner(currentMove+1);
6600 appData.animate = saveAnimate;
6602 /* check for the end of the game */
6603 if (currentMove >= forwardMostMove) {
6604 gameMode = PlayFromGameFile;
6606 SetTrainingModeOff();
6607 DisplayInformation(_("End of game"));
6610 DisplayError(_("Incorrect move"), 0);
6615 /* Ok, now we know that the move is good, so we can kill
6616 the previous line in Analysis Mode */
6617 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6618 && currentMove < forwardMostMove) {
6619 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6620 else forwardMostMove = currentMove;
6623 /* If we need the chess program but it's dead, restart it */
6624 ResurrectChessProgram();
6626 /* A user move restarts a paused game*/
6630 thinkOutput[0] = NULLCHAR;
6632 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6634 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6635 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6639 if (gameMode == BeginningOfGame) {
6640 if (appData.noChessProgram) {
6641 gameMode = EditGame;
6645 gameMode = MachinePlaysBlack;
6648 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6650 if (first.sendName) {
6651 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6652 SendToProgram(buf, &first);
6659 /* Relay move to ICS or chess engine */
6660 if (appData.icsActive) {
6661 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6662 gameMode == IcsExamining) {
6663 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6664 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6666 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6668 // also send plain move, in case ICS does not understand atomic claims
6669 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6673 if (first.sendTime && (gameMode == BeginningOfGame ||
6674 gameMode == MachinePlaysWhite ||
6675 gameMode == MachinePlaysBlack)) {
6676 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6678 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6679 // [HGM] book: if program might be playing, let it use book
6680 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6681 first.maybeThinking = TRUE;
6682 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6683 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6684 SendBoard(&first, currentMove+1);
6685 } else SendMoveToProgram(forwardMostMove-1, &first);
6686 if (currentMove == cmailOldMove + 1) {
6687 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6691 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6695 if(appData.testLegality)
6696 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6702 if (WhiteOnMove(currentMove)) {
6703 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6705 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6709 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6714 case MachinePlaysBlack:
6715 case MachinePlaysWhite:
6716 /* disable certain menu options while machine is thinking */
6717 SetMachineThinkingEnables();
6724 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6725 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6727 if(bookHit) { // [HGM] book: simulate book reply
6728 static char bookMove[MSG_SIZ]; // a bit generous?
6730 programStats.nodes = programStats.depth = programStats.time =
6731 programStats.score = programStats.got_only_move = 0;
6732 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6734 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6735 strcat(bookMove, bookHit);
6736 HandleMachineMove(bookMove, &first);
6742 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6749 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6750 Markers *m = (Markers *) closure;
6751 if(rf == fromY && ff == fromX)
6752 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6753 || kind == WhiteCapturesEnPassant
6754 || kind == BlackCapturesEnPassant);
6755 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6759 MarkTargetSquares(int clear)
6762 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6763 !appData.testLegality || gameMode == EditPosition) return;
6765 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6768 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6769 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6770 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6772 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6775 DrawPosition(TRUE, NULL);
6779 Explode(Board board, int fromX, int fromY, int toX, int toY)
6781 if(gameInfo.variant == VariantAtomic &&
6782 (board[toY][toX] != EmptySquare || // capture?
6783 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6784 board[fromY][fromX] == BlackPawn )
6786 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6792 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6794 int CanPromote(ChessSquare piece, int y)
6796 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6797 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6798 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6799 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6800 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6801 gameInfo.variant == VariantMakruk) return FALSE;
6802 return (piece == BlackPawn && y == 1 ||
6803 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6804 piece == BlackLance && y == 1 ||
6805 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6808 void LeftClick(ClickType clickType, int xPix, int yPix)
6811 Boolean saveAnimate;
6812 static int second = 0, promotionChoice = 0, clearFlag = 0;
6813 char promoChoice = NULLCHAR;
6816 if(appData.seekGraph && appData.icsActive && loggedOn &&
6817 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6818 SeekGraphClick(clickType, xPix, yPix, 0);
6822 if (clickType == Press) ErrorPopDown();
6824 x = EventToSquare(xPix, BOARD_WIDTH);
6825 y = EventToSquare(yPix, BOARD_HEIGHT);
6826 if (!flipView && y >= 0) {
6827 y = BOARD_HEIGHT - 1 - y;
6829 if (flipView && x >= 0) {
6830 x = BOARD_WIDTH - 1 - x;
6833 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6834 defaultPromoChoice = promoSweep;
6835 promoSweep = EmptySquare; // terminate sweep
6836 promoDefaultAltered = TRUE;
6837 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6840 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6841 if(clickType == Release) return; // ignore upclick of click-click destination
6842 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6843 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6844 if(gameInfo.holdingsWidth &&
6845 (WhiteOnMove(currentMove)
6846 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6847 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6848 // click in right holdings, for determining promotion piece
6849 ChessSquare p = boards[currentMove][y][x];
6850 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6851 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6852 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6853 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6858 DrawPosition(FALSE, boards[currentMove]);
6862 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6863 if(clickType == Press
6864 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6865 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6866 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6869 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6870 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6872 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6873 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6874 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6875 defaultPromoChoice = DefaultPromoChoice(side);
6878 autoQueen = appData.alwaysPromoteToQueen;
6882 gatingPiece = EmptySquare;
6883 if (clickType != Press) {
6884 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6885 DragPieceEnd(xPix, yPix); dragging = 0;
6886 DrawPosition(FALSE, NULL);
6890 fromX = x; fromY = y; toX = toY = -1;
6891 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6892 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6893 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6895 if (OKToStartUserMove(fromX, fromY)) {
6897 MarkTargetSquares(0);
6898 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6899 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6900 promoSweep = defaultPromoChoice;
6901 selectFlag = 0; lastX = xPix; lastY = yPix;
6902 Sweep(0); // Pawn that is going to promote: preview promotion piece
6903 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6905 if (appData.highlightDragging) {
6906 SetHighlights(fromX, fromY, -1, -1);
6908 } else fromX = fromY = -1;
6914 if (clickType == Press && gameMode != EditPosition) {
6919 // ignore off-board to clicks
6920 if(y < 0 || x < 0) return;
6922 /* Check if clicking again on the same color piece */
6923 fromP = boards[currentMove][fromY][fromX];
6924 toP = boards[currentMove][y][x];
6925 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6926 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6927 WhitePawn <= toP && toP <= WhiteKing &&
6928 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6929 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6930 (BlackPawn <= fromP && fromP <= BlackKing &&
6931 BlackPawn <= toP && toP <= BlackKing &&
6932 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6933 !(fromP == BlackKing && toP == BlackRook && frc))) {
6934 /* Clicked again on same color piece -- changed his mind */
6935 second = (x == fromX && y == fromY);
6936 promoDefaultAltered = FALSE;
6937 MarkTargetSquares(1);
6938 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6939 if (appData.highlightDragging) {
6940 SetHighlights(x, y, -1, -1);
6944 if (OKToStartUserMove(x, y)) {
6945 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6946 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6947 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6948 gatingPiece = boards[currentMove][fromY][fromX];
6949 else gatingPiece = EmptySquare;
6951 fromY = y; dragging = 1;
6952 MarkTargetSquares(0);
6953 DragPieceBegin(xPix, yPix, FALSE);
6954 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6955 promoSweep = defaultPromoChoice;
6956 selectFlag = 0; lastX = xPix; lastY = yPix;
6957 Sweep(0); // Pawn that is going to promote: preview promotion piece
6961 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6964 // ignore clicks on holdings
6965 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6968 if (clickType == Release && x == fromX && y == fromY) {
6969 DragPieceEnd(xPix, yPix); dragging = 0;
6971 // a deferred attempt to click-click move an empty square on top of a piece
6972 boards[currentMove][y][x] = EmptySquare;
6974 DrawPosition(FALSE, boards[currentMove]);
6975 fromX = fromY = -1; clearFlag = 0;
6978 if (appData.animateDragging) {
6979 /* Undo animation damage if any */
6980 DrawPosition(FALSE, NULL);
6983 /* Second up/down in same square; just abort move */
6986 gatingPiece = EmptySquare;
6989 ClearPremoveHighlights();
6991 /* First upclick in same square; start click-click mode */
6992 SetHighlights(x, y, -1, -1);
6999 /* we now have a different from- and (possibly off-board) to-square */
7000 /* Completed move */
7003 saveAnimate = appData.animate;
7004 MarkTargetSquares(1);
7005 if (clickType == Press) {
7006 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7007 // must be Edit Position mode with empty-square selected
7008 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7009 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7012 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7013 ChessSquare piece = boards[currentMove][fromY][fromX];
7014 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7015 promoSweep = defaultPromoChoice;
7016 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7017 selectFlag = 0; lastX = xPix; lastY = yPix;
7018 Sweep(0); // Pawn that is going to promote: preview promotion piece
7019 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7020 DrawPosition(FALSE, boards[currentMove]);
7023 /* Finish clickclick move */
7024 if (appData.animate || appData.highlightLastMove) {
7025 SetHighlights(fromX, fromY, toX, toY);
7030 /* Finish drag move */
7031 if (appData.highlightLastMove) {
7032 SetHighlights(fromX, fromY, toX, toY);
7036 DragPieceEnd(xPix, yPix); dragging = 0;
7037 /* Don't animate move and drag both */
7038 appData.animate = FALSE;
7041 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7042 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7043 ChessSquare piece = boards[currentMove][fromY][fromX];
7044 if(gameMode == EditPosition && piece != EmptySquare &&
7045 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7048 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7049 n = PieceToNumber(piece - (int)BlackPawn);
7050 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7051 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7052 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7054 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7055 n = PieceToNumber(piece);
7056 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7057 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7058 boards[currentMove][n][BOARD_WIDTH-2]++;
7060 boards[currentMove][fromY][fromX] = EmptySquare;
7064 DrawPosition(TRUE, boards[currentMove]);
7068 // off-board moves should not be highlighted
7069 if(x < 0 || y < 0) ClearHighlights();
7071 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7073 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7074 SetHighlights(fromX, fromY, toX, toY);
7075 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7076 // [HGM] super: promotion to captured piece selected from holdings
7077 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7078 promotionChoice = TRUE;
7079 // kludge follows to temporarily execute move on display, without promoting yet
7080 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7081 boards[currentMove][toY][toX] = p;
7082 DrawPosition(FALSE, boards[currentMove]);
7083 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7084 boards[currentMove][toY][toX] = q;
7085 DisplayMessage("Click in holdings to choose piece", "");
7090 int oldMove = currentMove;
7091 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7092 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7093 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7094 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7095 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7096 DrawPosition(TRUE, boards[currentMove]);
7099 appData.animate = saveAnimate;
7100 if (appData.animate || appData.animateDragging) {
7101 /* Undo animation damage if needed */
7102 DrawPosition(FALSE, NULL);
7106 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7107 { // front-end-free part taken out of PieceMenuPopup
7108 int whichMenu; int xSqr, ySqr;
7110 if(seekGraphUp) { // [HGM] seekgraph
7111 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7112 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7116 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7117 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7118 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7119 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7120 if(action == Press) {
7121 originalFlip = flipView;
7122 flipView = !flipView; // temporarily flip board to see game from partners perspective
7123 DrawPosition(TRUE, partnerBoard);
7124 DisplayMessage(partnerStatus, "");
7126 } else if(action == Release) {
7127 flipView = originalFlip;
7128 DrawPosition(TRUE, boards[currentMove]);
7134 xSqr = EventToSquare(x, BOARD_WIDTH);
7135 ySqr = EventToSquare(y, BOARD_HEIGHT);
7136 if (action == Release) {
7137 if(pieceSweep != EmptySquare) {
7138 EditPositionMenuEvent(pieceSweep, toX, toY);
7139 pieceSweep = EmptySquare;
7140 } else UnLoadPV(); // [HGM] pv
7142 if (action != Press) return -2; // return code to be ignored
7145 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7147 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7148 if (xSqr < 0 || ySqr < 0) return -1;
7149 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7150 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7151 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7152 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7156 if(!appData.icsEngineAnalyze) return -1;
7157 case IcsPlayingWhite:
7158 case IcsPlayingBlack:
7159 if(!appData.zippyPlay) goto noZip;
7162 case MachinePlaysWhite:
7163 case MachinePlaysBlack:
7164 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7165 if (!appData.dropMenu) {
7167 return 2; // flag front-end to grab mouse events
7169 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7170 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7173 if (xSqr < 0 || ySqr < 0) return -1;
7174 if (!appData.dropMenu || appData.testLegality &&
7175 gameInfo.variant != VariantBughouse &&
7176 gameInfo.variant != VariantCrazyhouse) return -1;
7177 whichMenu = 1; // drop menu
7183 if (((*fromX = xSqr) < 0) ||
7184 ((*fromY = ySqr) < 0)) {
7185 *fromX = *fromY = -1;
7189 *fromX = BOARD_WIDTH - 1 - *fromX;
7191 *fromY = BOARD_HEIGHT - 1 - *fromY;
7196 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7198 // char * hint = lastHint;
7199 FrontEndProgramStats stats;
7201 stats.which = cps == &first ? 0 : 1;
7202 stats.depth = cpstats->depth;
7203 stats.nodes = cpstats->nodes;
7204 stats.score = cpstats->score;
7205 stats.time = cpstats->time;
7206 stats.pv = cpstats->movelist;
7207 stats.hint = lastHint;
7208 stats.an_move_index = 0;
7209 stats.an_move_count = 0;
7211 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7212 stats.hint = cpstats->move_name;
7213 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7214 stats.an_move_count = cpstats->nr_moves;
7217 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
7219 SetProgramStats( &stats );
7223 ClearEngineOutputPane(int which)
7225 static FrontEndProgramStats dummyStats;
7226 dummyStats.which = which;
7227 dummyStats.pv = "#";
7228 SetProgramStats( &dummyStats );
7231 #define MAXPLAYERS 500
7234 TourneyStandings(int display)
7236 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7237 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7238 char result, *p, *names[MAXPLAYERS];
7240 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7241 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7242 names[0] = p = strdup(appData.participants);
7243 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7245 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7247 while(result = appData.results[nr]) {
7248 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7249 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7250 wScore = bScore = 0;
7252 case '+': wScore = 2; break;
7253 case '-': bScore = 2; break;
7254 case '=': wScore = bScore = 1; break;
7256 case '*': return strdup("busy"); // tourney not finished
7264 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7265 for(w=0; w<nPlayers; w++) {
7267 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7268 ranking[w] = b; points[w] = bScore; score[b] = -2;
7270 p = malloc(nPlayers*34+1);
7271 for(w=0; w<nPlayers && w<display; w++)
7272 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7278 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7279 { // count all piece types
7281 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7282 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7283 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7286 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7287 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7288 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7289 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7290 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7291 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7296 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7298 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7299 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7301 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7302 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7303 if(myPawns == 2 && nMine == 3) // KPP
7304 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7305 if(myPawns == 1 && nMine == 2) // KP
7306 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7307 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7308 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7309 if(myPawns) return FALSE;
7310 if(pCnt[WhiteRook+side])
7311 return pCnt[BlackRook-side] ||
7312 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7313 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7314 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7315 if(pCnt[WhiteCannon+side]) {
7316 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7317 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7319 if(pCnt[WhiteKnight+side])
7320 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7325 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7327 VariantClass v = gameInfo.variant;
7329 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7330 if(v == VariantShatranj) return TRUE; // always winnable through baring
7331 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7332 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7334 if(v == VariantXiangqi) {
7335 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7337 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7338 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7339 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7340 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7341 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7342 if(stale) // we have at least one last-rank P plus perhaps C
7343 return majors // KPKX
7344 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7346 return pCnt[WhiteFerz+side] // KCAK
7347 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7348 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7349 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7351 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7352 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7354 if(nMine == 1) return FALSE; // bare King
7355 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
7356 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7357 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7358 // by now we have King + 1 piece (or multiple Bishops on the same color)
7359 if(pCnt[WhiteKnight+side])
7360 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7361 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7362 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7364 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7365 if(pCnt[WhiteAlfil+side])
7366 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7367 if(pCnt[WhiteWazir+side])
7368 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7375 CompareWithRights(Board b1, Board b2)
7378 if(!CompareBoards(b1, b2)) return FALSE;
7379 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7380 /* compare castling rights */
7381 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7382 rights++; /* King lost rights, while rook still had them */
7383 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7384 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7385 rights++; /* but at least one rook lost them */
7387 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7389 if( b1[CASTLING][5] != NoRights ) {
7390 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7397 Adjudicate(ChessProgramState *cps)
7398 { // [HGM] some adjudications useful with buggy engines
7399 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7400 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7401 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7402 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7403 int k, count = 0; static int bare = 1;
7404 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7405 Boolean canAdjudicate = !appData.icsActive;
7407 // most tests only when we understand the game, i.e. legality-checking on
7408 if( appData.testLegality )
7409 { /* [HGM] Some more adjudications for obstinate engines */
7410 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7411 static int moveCount = 6;
7413 char *reason = NULL;
7415 /* Count what is on board. */
7416 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7418 /* Some material-based adjudications that have to be made before stalemate test */
7419 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7420 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7421 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7422 if(canAdjudicate && appData.checkMates) {
7424 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7425 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7426 "Xboard adjudication: King destroyed", GE_XBOARD );
7431 /* Bare King in Shatranj (loses) or Losers (wins) */
7432 if( nrW == 1 || nrB == 1) {
7433 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7434 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7435 if(canAdjudicate && appData.checkMates) {
7437 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7438 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7439 "Xboard adjudication: Bare king", GE_XBOARD );
7443 if( gameInfo.variant == VariantShatranj && --bare < 0)
7445 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7446 if(canAdjudicate && appData.checkMates) {
7447 /* but only adjudicate if adjudication enabled */
7449 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7450 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7451 "Xboard adjudication: Bare king", GE_XBOARD );
7458 // don't wait for engine to announce game end if we can judge ourselves
7459 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7461 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7462 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7463 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7464 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7467 reason = "Xboard adjudication: 3rd check";
7468 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7478 reason = "Xboard adjudication: Stalemate";
7479 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7480 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7481 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7482 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7483 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7484 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7485 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7486 EP_CHECKMATE : EP_WINS);
7487 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7488 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7492 reason = "Xboard adjudication: Checkmate";
7493 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7497 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7499 result = GameIsDrawn; break;
7501 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7503 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7507 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7509 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7510 GameEnds( result, reason, GE_XBOARD );
7514 /* Next absolutely insufficient mating material. */
7515 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7516 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7517 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7519 /* always flag draws, for judging claims */
7520 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7522 if(canAdjudicate && appData.materialDraws) {
7523 /* but only adjudicate them if adjudication enabled */
7524 if(engineOpponent) {
7525 SendToProgram("force\n", engineOpponent); // suppress reply
7526 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7528 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7533 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7534 if(gameInfo.variant == VariantXiangqi ?
7535 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7537 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7538 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7539 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7540 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7542 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7543 { /* if the first 3 moves do not show a tactical win, declare draw */
7544 if(engineOpponent) {
7545 SendToProgram("force\n", engineOpponent); // suppress reply
7546 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7548 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7551 } else moveCount = 6;
7553 if (appData.debugMode) { int i;
7554 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7555 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7556 appData.drawRepeats);
7557 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7558 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7562 // Repetition draws and 50-move rule can be applied independently of legality testing
7564 /* Check for rep-draws */
7566 for(k = forwardMostMove-2;
7567 k>=backwardMostMove && k>=forwardMostMove-100 &&
7568 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7569 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7572 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7573 /* compare castling rights */
7574 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7575 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7576 rights++; /* King lost rights, while rook still had them */
7577 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7578 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7579 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7580 rights++; /* but at least one rook lost them */
7582 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7583 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7585 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7586 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7587 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7590 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7591 && appData.drawRepeats > 1) {
7592 /* adjudicate after user-specified nr of repeats */
7593 int result = GameIsDrawn;
7594 char *details = "XBoard adjudication: repetition draw";
7595 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7596 // [HGM] xiangqi: check for forbidden perpetuals
7597 int m, ourPerpetual = 1, hisPerpetual = 1;
7598 for(m=forwardMostMove; m>k; m-=2) {
7599 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7600 ourPerpetual = 0; // the current mover did not always check
7601 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7602 hisPerpetual = 0; // the opponent did not always check
7604 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7605 ourPerpetual, hisPerpetual);
7606 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7607 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7608 details = "Xboard adjudication: perpetual checking";
7610 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7611 break; // (or we would have caught him before). Abort repetition-checking loop.
7613 // Now check for perpetual chases
7614 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7615 hisPerpetual = PerpetualChase(k, forwardMostMove);
7616 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7617 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7618 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7619 details = "Xboard adjudication: perpetual chasing";
7621 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7622 break; // Abort repetition-checking loop.
7624 // if neither of us is checking or chasing all the time, or both are, it is draw
7626 if(engineOpponent) {
7627 SendToProgram("force\n", engineOpponent); // suppress reply
7628 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7630 GameEnds( result, details, GE_XBOARD );
7633 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7634 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7638 /* Now we test for 50-move draws. Determine ply count */
7639 count = forwardMostMove;
7640 /* look for last irreversble move */
7641 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7643 /* if we hit starting position, add initial plies */
7644 if( count == backwardMostMove )
7645 count -= initialRulePlies;
7646 count = forwardMostMove - count;
7647 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7648 // adjust reversible move counter for checks in Xiangqi
7649 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7650 if(i < backwardMostMove) i = backwardMostMove;
7651 while(i <= forwardMostMove) {
7652 lastCheck = inCheck; // check evasion does not count
7653 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7654 if(inCheck || lastCheck) count--; // check does not count
7659 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7660 /* this is used to judge if draw claims are legal */
7661 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7662 if(engineOpponent) {
7663 SendToProgram("force\n", engineOpponent); // suppress reply
7664 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7666 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7670 /* if draw offer is pending, treat it as a draw claim
7671 * when draw condition present, to allow engines a way to
7672 * claim draws before making their move to avoid a race
7673 * condition occurring after their move
7675 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7677 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7678 p = "Draw claim: 50-move rule";
7679 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7680 p = "Draw claim: 3-fold repetition";
7681 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7682 p = "Draw claim: insufficient mating material";
7683 if( p != NULL && canAdjudicate) {
7684 if(engineOpponent) {
7685 SendToProgram("force\n", engineOpponent); // suppress reply
7686 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7688 GameEnds( GameIsDrawn, p, GE_XBOARD );
7693 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7694 if(engineOpponent) {
7695 SendToProgram("force\n", engineOpponent); // suppress reply
7696 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7698 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7704 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7705 { // [HGM] book: this routine intercepts moves to simulate book replies
7706 char *bookHit = NULL;
7708 //first determine if the incoming move brings opponent into his book
7709 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7710 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7711 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7712 if(bookHit != NULL && !cps->bookSuspend) {
7713 // make sure opponent is not going to reply after receiving move to book position
7714 SendToProgram("force\n", cps);
7715 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7717 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7718 // now arrange restart after book miss
7720 // after a book hit we never send 'go', and the code after the call to this routine
7721 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7722 char buf[MSG_SIZ], *move = bookHit;
7724 int fromX, fromY, toX, toY;
7728 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7729 &fromX, &fromY, &toX, &toY, &promoChar)) {
7730 (void) CoordsToAlgebraic(boards[forwardMostMove],
7731 PosFlags(forwardMostMove),
7732 fromY, fromX, toY, toX, promoChar, move);
7734 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7738 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7739 SendToProgram(buf, cps);
7740 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7741 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7742 SendToProgram("go\n", cps);
7743 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7744 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7745 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7746 SendToProgram("go\n", cps);
7747 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7749 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7753 ChessProgramState *savedState;
7754 void DeferredBookMove(void)
7756 if(savedState->lastPing != savedState->lastPong)
7757 ScheduleDelayedEvent(DeferredBookMove, 10);
7759 HandleMachineMove(savedMessage, savedState);
7762 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7765 HandleMachineMove(message, cps)
7767 ChessProgramState *cps;
7769 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7770 char realname[MSG_SIZ];
7771 int fromX, fromY, toX, toY;
7778 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7779 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7780 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7781 DisplayError(_("Invalid pairing from pairing engine"), 0);
7784 pairingReceived = 1;
7786 return; // Skim the pairing messages here.
7791 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7793 * Kludge to ignore BEL characters
7795 while (*message == '\007') message++;
7798 * [HGM] engine debug message: ignore lines starting with '#' character
7800 if(cps->debug && *message == '#') return;
7803 * Look for book output
7805 if (cps == &first && bookRequested) {
7806 if (message[0] == '\t' || message[0] == ' ') {
7807 /* Part of the book output is here; append it */
7808 strcat(bookOutput, message);
7809 strcat(bookOutput, " \n");
7811 } else if (bookOutput[0] != NULLCHAR) {
7812 /* All of book output has arrived; display it */
7813 char *p = bookOutput;
7814 while (*p != NULLCHAR) {
7815 if (*p == '\t') *p = ' ';
7818 DisplayInformation(bookOutput);
7819 bookRequested = FALSE;
7820 /* Fall through to parse the current output */
7825 * Look for machine move.
7827 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7828 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7830 /* This method is only useful on engines that support ping */
7831 if (cps->lastPing != cps->lastPong) {
7832 if (gameMode == BeginningOfGame) {
7833 /* Extra move from before last new; ignore */
7834 if (appData.debugMode) {
7835 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7838 if (appData.debugMode) {
7839 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7840 cps->which, gameMode);
7843 SendToProgram("undo\n", cps);
7849 case BeginningOfGame:
7850 /* Extra move from before last reset; ignore */
7851 if (appData.debugMode) {
7852 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7859 /* Extra move after we tried to stop. The mode test is
7860 not a reliable way of detecting this problem, but it's
7861 the best we can do on engines that don't support ping.
7863 if (appData.debugMode) {
7864 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7865 cps->which, gameMode);
7867 SendToProgram("undo\n", cps);
7870 case MachinePlaysWhite:
7871 case IcsPlayingWhite:
7872 machineWhite = TRUE;
7875 case MachinePlaysBlack:
7876 case IcsPlayingBlack:
7877 machineWhite = FALSE;
7880 case TwoMachinesPlay:
7881 machineWhite = (cps->twoMachinesColor[0] == 'w');
7884 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7885 if (appData.debugMode) {
7887 "Ignoring move out of turn by %s, gameMode %d"
7888 ", forwardMost %d\n",
7889 cps->which, gameMode, forwardMostMove);
7894 if (appData.debugMode) { int f = forwardMostMove;
7895 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7896 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7897 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7899 if(cps->alphaRank) AlphaRank(machineMove, 4);
7900 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7901 &fromX, &fromY, &toX, &toY, &promoChar)) {
7902 /* Machine move could not be parsed; ignore it. */
7903 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7904 machineMove, _(cps->which));
7905 DisplayError(buf1, 0);
7906 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7907 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7908 if (gameMode == TwoMachinesPlay) {
7909 GameEnds(machineWhite ? BlackWins : WhiteWins,
7915 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7916 /* So we have to redo legality test with true e.p. status here, */
7917 /* to make sure an illegal e.p. capture does not slip through, */
7918 /* to cause a forfeit on a justified illegal-move complaint */
7919 /* of the opponent. */
7920 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7922 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7923 fromY, fromX, toY, toX, promoChar);
7924 if (appData.debugMode) {
7926 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7927 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7928 fprintf(debugFP, "castling rights\n");
7930 if(moveType == IllegalMove) {
7931 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7932 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7933 GameEnds(machineWhite ? BlackWins : WhiteWins,
7936 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7937 /* [HGM] Kludge to handle engines that send FRC-style castling
7938 when they shouldn't (like TSCP-Gothic) */
7940 case WhiteASideCastleFR:
7941 case BlackASideCastleFR:
7943 currentMoveString[2]++;
7945 case WhiteHSideCastleFR:
7946 case BlackHSideCastleFR:
7948 currentMoveString[2]--;
7950 default: ; // nothing to do, but suppresses warning of pedantic compilers
7953 hintRequested = FALSE;
7954 lastHint[0] = NULLCHAR;
7955 bookRequested = FALSE;
7956 /* Program may be pondering now */
7957 cps->maybeThinking = TRUE;
7958 if (cps->sendTime == 2) cps->sendTime = 1;
7959 if (cps->offeredDraw) cps->offeredDraw--;
7961 /* [AS] Save move info*/
7962 pvInfoList[ forwardMostMove ].score = programStats.score;
7963 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7964 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7966 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7968 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7969 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7972 while( count < adjudicateLossPlies ) {
7973 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7976 score = -score; /* Flip score for winning side */
7979 if( score > adjudicateLossThreshold ) {
7986 if( count >= adjudicateLossPlies ) {
7987 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7989 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7990 "Xboard adjudication",
7997 if(Adjudicate(cps)) {
7998 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7999 return; // [HGM] adjudicate: for all automatic game ends
8003 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8005 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8006 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8008 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8010 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8012 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8013 char buf[3*MSG_SIZ];
8015 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8016 programStats.score / 100.,
8018 programStats.time / 100.,
8019 (unsigned int)programStats.nodes,
8020 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8021 programStats.movelist);
8023 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8028 /* [AS] Clear stats for next move */
8029 ClearProgramStats();
8030 thinkOutput[0] = NULLCHAR;
8031 hiddenThinkOutputState = 0;
8034 if (gameMode == TwoMachinesPlay) {
8035 /* [HGM] relaying draw offers moved to after reception of move */
8036 /* and interpreting offer as claim if it brings draw condition */
8037 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8038 SendToProgram("draw\n", cps->other);
8040 if (cps->other->sendTime) {
8041 SendTimeRemaining(cps->other,
8042 cps->other->twoMachinesColor[0] == 'w');
8044 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8045 if (firstMove && !bookHit) {
8047 if (cps->other->useColors) {
8048 SendToProgram(cps->other->twoMachinesColor, cps->other);
8050 SendToProgram("go\n", cps->other);
8052 cps->other->maybeThinking = TRUE;
8055 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8057 if (!pausing && appData.ringBellAfterMoves) {
8062 * Reenable menu items that were disabled while
8063 * machine was thinking
8065 if (gameMode != TwoMachinesPlay)
8066 SetUserThinkingEnables();
8068 // [HGM] book: after book hit opponent has received move and is now in force mode
8069 // force the book reply into it, and then fake that it outputted this move by jumping
8070 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8072 static char bookMove[MSG_SIZ]; // a bit generous?
8074 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8075 strcat(bookMove, bookHit);
8078 programStats.nodes = programStats.depth = programStats.time =
8079 programStats.score = programStats.got_only_move = 0;
8080 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8082 if(cps->lastPing != cps->lastPong) {
8083 savedMessage = message; // args for deferred call
8085 ScheduleDelayedEvent(DeferredBookMove, 10);
8094 /* Set special modes for chess engines. Later something general
8095 * could be added here; for now there is just one kludge feature,
8096 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8097 * when "xboard" is given as an interactive command.
8099 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8100 cps->useSigint = FALSE;
8101 cps->useSigterm = FALSE;
8103 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8104 ParseFeatures(message+8, cps);
8105 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8108 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8109 int dummy, s=6; char buf[MSG_SIZ];
8110 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8111 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8112 ParseFEN(boards[0], &dummy, message+s);
8113 DrawPosition(TRUE, boards[0]);
8114 startedFromSetupPosition = TRUE;
8117 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8118 * want this, I was asked to put it in, and obliged.
8120 if (!strncmp(message, "setboard ", 9)) {
8121 Board initial_position;
8123 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8125 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8126 DisplayError(_("Bad FEN received from engine"), 0);
8130 CopyBoard(boards[0], initial_position);
8131 initialRulePlies = FENrulePlies;
8132 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8133 else gameMode = MachinePlaysBlack;
8134 DrawPosition(FALSE, boards[currentMove]);
8140 * Look for communication commands
8142 if (!strncmp(message, "telluser ", 9)) {
8143 if(message[9] == '\\' && message[10] == '\\')
8144 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8146 DisplayNote(message + 9);
8149 if (!strncmp(message, "tellusererror ", 14)) {
8151 if(message[14] == '\\' && message[15] == '\\')
8152 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8154 DisplayError(message + 14, 0);
8157 if (!strncmp(message, "tellopponent ", 13)) {
8158 if (appData.icsActive) {
8160 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8164 DisplayNote(message + 13);
8168 if (!strncmp(message, "tellothers ", 11)) {
8169 if (appData.icsActive) {
8171 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8177 if (!strncmp(message, "tellall ", 8)) {
8178 if (appData.icsActive) {
8180 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8184 DisplayNote(message + 8);
8188 if (strncmp(message, "warning", 7) == 0) {
8189 /* Undocumented feature, use tellusererror in new code */
8190 DisplayError(message, 0);
8193 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8194 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8195 strcat(realname, " query");
8196 AskQuestion(realname, buf2, buf1, cps->pr);
8199 /* Commands from the engine directly to ICS. We don't allow these to be
8200 * sent until we are logged on. Crafty kibitzes have been known to
8201 * interfere with the login process.
8204 if (!strncmp(message, "tellics ", 8)) {
8205 SendToICS(message + 8);
8209 if (!strncmp(message, "tellicsnoalias ", 15)) {
8210 SendToICS(ics_prefix);
8211 SendToICS(message + 15);
8215 /* The following are for backward compatibility only */
8216 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8217 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8218 SendToICS(ics_prefix);
8224 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8228 * If the move is illegal, cancel it and redraw the board.
8229 * Also deal with other error cases. Matching is rather loose
8230 * here to accommodate engines written before the spec.
8232 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8233 strncmp(message, "Error", 5) == 0) {
8234 if (StrStr(message, "name") ||
8235 StrStr(message, "rating") || StrStr(message, "?") ||
8236 StrStr(message, "result") || StrStr(message, "board") ||
8237 StrStr(message, "bk") || StrStr(message, "computer") ||
8238 StrStr(message, "variant") || StrStr(message, "hint") ||
8239 StrStr(message, "random") || StrStr(message, "depth") ||
8240 StrStr(message, "accepted")) {
8243 if (StrStr(message, "protover")) {
8244 /* Program is responding to input, so it's apparently done
8245 initializing, and this error message indicates it is
8246 protocol version 1. So we don't need to wait any longer
8247 for it to initialize and send feature commands. */
8248 FeatureDone(cps, 1);
8249 cps->protocolVersion = 1;
8252 cps->maybeThinking = FALSE;
8254 if (StrStr(message, "draw")) {
8255 /* Program doesn't have "draw" command */
8256 cps->sendDrawOffers = 0;
8259 if (cps->sendTime != 1 &&
8260 (StrStr(message, "time") || StrStr(message, "otim"))) {
8261 /* Program apparently doesn't have "time" or "otim" command */
8265 if (StrStr(message, "analyze")) {
8266 cps->analysisSupport = FALSE;
8267 cps->analyzing = FALSE;
8268 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8269 EditGameEvent(); // [HGM] try to preserve loaded game
8270 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8271 DisplayError(buf2, 0);
8274 if (StrStr(message, "(no matching move)st")) {
8275 /* Special kludge for GNU Chess 4 only */
8276 cps->stKludge = TRUE;
8277 SendTimeControl(cps, movesPerSession, timeControl,
8278 timeIncrement, appData.searchDepth,
8282 if (StrStr(message, "(no matching move)sd")) {
8283 /* Special kludge for GNU Chess 4 only */
8284 cps->sdKludge = TRUE;
8285 SendTimeControl(cps, movesPerSession, timeControl,
8286 timeIncrement, appData.searchDepth,
8290 if (!StrStr(message, "llegal")) {
8293 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8294 gameMode == IcsIdle) return;
8295 if (forwardMostMove <= backwardMostMove) return;
8296 if (pausing) PauseEvent();
8297 if(appData.forceIllegal) {
8298 // [HGM] illegal: machine refused move; force position after move into it
8299 SendToProgram("force\n", cps);
8300 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8301 // we have a real problem now, as SendBoard will use the a2a3 kludge
8302 // when black is to move, while there might be nothing on a2 or black
8303 // might already have the move. So send the board as if white has the move.
8304 // But first we must change the stm of the engine, as it refused the last move
8305 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8306 if(WhiteOnMove(forwardMostMove)) {
8307 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8308 SendBoard(cps, forwardMostMove); // kludgeless board
8310 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8311 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8312 SendBoard(cps, forwardMostMove+1); // kludgeless board
8314 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8315 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8316 gameMode == TwoMachinesPlay)
8317 SendToProgram("go\n", cps);
8320 if (gameMode == PlayFromGameFile) {
8321 /* Stop reading this game file */
8322 gameMode = EditGame;
8325 /* [HGM] illegal-move claim should forfeit game when Xboard */
8326 /* only passes fully legal moves */
8327 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8328 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8329 "False illegal-move claim", GE_XBOARD );
8330 return; // do not take back move we tested as valid
8332 currentMove = forwardMostMove-1;
8333 DisplayMove(currentMove-1); /* before DisplayMoveError */
8334 SwitchClocks(forwardMostMove-1); // [HGM] race
8335 DisplayBothClocks();
8336 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8337 parseList[currentMove], _(cps->which));
8338 DisplayMoveError(buf1);
8339 DrawPosition(FALSE, boards[currentMove]);
8342 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8343 /* Program has a broken "time" command that
8344 outputs a string not ending in newline.
8350 * If chess program startup fails, exit with an error message.
8351 * Attempts to recover here are futile.
8353 if ((StrStr(message, "unknown host") != NULL)
8354 || (StrStr(message, "No remote directory") != NULL)
8355 || (StrStr(message, "not found") != NULL)
8356 || (StrStr(message, "No such file") != NULL)
8357 || (StrStr(message, "can't alloc") != NULL)
8358 || (StrStr(message, "Permission denied") != NULL)) {
8360 cps->maybeThinking = FALSE;
8361 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8362 _(cps->which), cps->program, cps->host, message);
8363 RemoveInputSource(cps->isr);
8364 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8365 if(cps == &first) appData.noChessProgram = TRUE;
8366 DisplayError(buf1, 0);
8372 * Look for hint output
8374 if (sscanf(message, "Hint: %s", buf1) == 1) {
8375 if (cps == &first && hintRequested) {
8376 hintRequested = FALSE;
8377 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8378 &fromX, &fromY, &toX, &toY, &promoChar)) {
8379 (void) CoordsToAlgebraic(boards[forwardMostMove],
8380 PosFlags(forwardMostMove),
8381 fromY, fromX, toY, toX, promoChar, buf1);
8382 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8383 DisplayInformation(buf2);
8385 /* Hint move could not be parsed!? */
8386 snprintf(buf2, sizeof(buf2),
8387 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8388 buf1, _(cps->which));
8389 DisplayError(buf2, 0);
8392 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8398 * Ignore other messages if game is not in progress
8400 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8401 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8404 * look for win, lose, draw, or draw offer
8406 if (strncmp(message, "1-0", 3) == 0) {
8407 char *p, *q, *r = "";
8408 p = strchr(message, '{');
8416 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8418 } else if (strncmp(message, "0-1", 3) == 0) {
8419 char *p, *q, *r = "";
8420 p = strchr(message, '{');
8428 /* Kludge for Arasan 4.1 bug */
8429 if (strcmp(r, "Black resigns") == 0) {
8430 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8433 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8435 } else if (strncmp(message, "1/2", 3) == 0) {
8436 char *p, *q, *r = "";
8437 p = strchr(message, '{');
8446 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8449 } else if (strncmp(message, "White resign", 12) == 0) {
8450 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8452 } else if (strncmp(message, "Black resign", 12) == 0) {
8453 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8455 } else if (strncmp(message, "White matches", 13) == 0 ||
8456 strncmp(message, "Black matches", 13) == 0 ) {
8457 /* [HGM] ignore GNUShogi noises */
8459 } else if (strncmp(message, "White", 5) == 0 &&
8460 message[5] != '(' &&
8461 StrStr(message, "Black") == NULL) {
8462 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8464 } else if (strncmp(message, "Black", 5) == 0 &&
8465 message[5] != '(') {
8466 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8468 } else if (strcmp(message, "resign") == 0 ||
8469 strcmp(message, "computer resigns") == 0) {
8471 case MachinePlaysBlack:
8472 case IcsPlayingBlack:
8473 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8475 case MachinePlaysWhite:
8476 case IcsPlayingWhite:
8477 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8479 case TwoMachinesPlay:
8480 if (cps->twoMachinesColor[0] == 'w')
8481 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8483 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8490 } else if (strncmp(message, "opponent mates", 14) == 0) {
8492 case MachinePlaysBlack:
8493 case IcsPlayingBlack:
8494 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8496 case MachinePlaysWhite:
8497 case IcsPlayingWhite:
8498 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8500 case TwoMachinesPlay:
8501 if (cps->twoMachinesColor[0] == 'w')
8502 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8504 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8511 } else if (strncmp(message, "computer mates", 14) == 0) {
8513 case MachinePlaysBlack:
8514 case IcsPlayingBlack:
8515 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8517 case MachinePlaysWhite:
8518 case IcsPlayingWhite:
8519 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8521 case TwoMachinesPlay:
8522 if (cps->twoMachinesColor[0] == 'w')
8523 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8525 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8532 } else if (strncmp(message, "checkmate", 9) == 0) {
8533 if (WhiteOnMove(forwardMostMove)) {
8534 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8536 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539 } else if (strstr(message, "Draw") != NULL ||
8540 strstr(message, "game is a draw") != NULL) {
8541 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8543 } else if (strstr(message, "offer") != NULL &&
8544 strstr(message, "draw") != NULL) {
8546 if (appData.zippyPlay && first.initDone) {
8547 /* Relay offer to ICS */
8548 SendToICS(ics_prefix);
8549 SendToICS("draw\n");
8552 cps->offeredDraw = 2; /* valid until this engine moves twice */
8553 if (gameMode == TwoMachinesPlay) {
8554 if (cps->other->offeredDraw) {
8555 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8556 /* [HGM] in two-machine mode we delay relaying draw offer */
8557 /* until after we also have move, to see if it is really claim */
8559 } else if (gameMode == MachinePlaysWhite ||
8560 gameMode == MachinePlaysBlack) {
8561 if (userOfferedDraw) {
8562 DisplayInformation(_("Machine accepts your draw offer"));
8563 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8565 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8572 * Look for thinking output
8574 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8575 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8577 int plylev, mvleft, mvtot, curscore, time;
8578 char mvname[MOVE_LEN];
8582 int prefixHint = FALSE;
8583 mvname[0] = NULLCHAR;
8586 case MachinePlaysBlack:
8587 case IcsPlayingBlack:
8588 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8590 case MachinePlaysWhite:
8591 case IcsPlayingWhite:
8592 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8597 case IcsObserving: /* [DM] icsEngineAnalyze */
8598 if (!appData.icsEngineAnalyze) ignore = TRUE;
8600 case TwoMachinesPlay:
8601 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8611 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8613 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8614 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8616 if (plyext != ' ' && plyext != '\t') {
8620 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8621 if( cps->scoreIsAbsolute &&
8622 ( gameMode == MachinePlaysBlack ||
8623 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8624 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8625 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8626 !WhiteOnMove(currentMove)
8629 curscore = -curscore;
8632 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8634 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8637 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8638 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8639 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8640 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8641 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8642 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8644 } else DisplayError("failed writing PV", 0);
8647 tempStats.depth = plylev;
8648 tempStats.nodes = nodes;
8649 tempStats.time = time;
8650 tempStats.score = curscore;
8651 tempStats.got_only_move = 0;
8653 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8656 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8657 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8658 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8659 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8660 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8661 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8662 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8663 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8666 /* Buffer overflow protection */
8667 if (pv[0] != NULLCHAR) {
8668 if (strlen(pv) >= sizeof(tempStats.movelist)
8669 && appData.debugMode) {
8671 "PV is too long; using the first %u bytes.\n",
8672 (unsigned) sizeof(tempStats.movelist) - 1);
8675 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8677 sprintf(tempStats.movelist, " no PV\n");
8680 if (tempStats.seen_stat) {
8681 tempStats.ok_to_send = 1;
8684 if (strchr(tempStats.movelist, '(') != NULL) {
8685 tempStats.line_is_book = 1;
8686 tempStats.nr_moves = 0;
8687 tempStats.moves_left = 0;
8689 tempStats.line_is_book = 0;
8692 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8693 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8695 SendProgramStatsToFrontend( cps, &tempStats );
8698 [AS] Protect the thinkOutput buffer from overflow... this
8699 is only useful if buf1 hasn't overflowed first!
8701 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8703 (gameMode == TwoMachinesPlay ?
8704 ToUpper(cps->twoMachinesColor[0]) : ' '),
8705 ((double) curscore) / 100.0,
8706 prefixHint ? lastHint : "",
8707 prefixHint ? " " : "" );
8709 if( buf1[0] != NULLCHAR ) {
8710 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8712 if( strlen(pv) > max_len ) {
8713 if( appData.debugMode) {
8714 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8716 pv[max_len+1] = '\0';
8719 strcat( thinkOutput, pv);
8722 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8723 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8724 DisplayMove(currentMove - 1);
8728 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8729 /* crafty (9.25+) says "(only move) <move>"
8730 * if there is only 1 legal move
8732 sscanf(p, "(only move) %s", buf1);
8733 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8734 sprintf(programStats.movelist, "%s (only move)", buf1);
8735 programStats.depth = 1;
8736 programStats.nr_moves = 1;
8737 programStats.moves_left = 1;
8738 programStats.nodes = 1;
8739 programStats.time = 1;
8740 programStats.got_only_move = 1;
8742 /* Not really, but we also use this member to
8743 mean "line isn't going to change" (Crafty
8744 isn't searching, so stats won't change) */
8745 programStats.line_is_book = 1;
8747 SendProgramStatsToFrontend( cps, &programStats );
8749 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8750 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8751 DisplayMove(currentMove - 1);
8754 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8755 &time, &nodes, &plylev, &mvleft,
8756 &mvtot, mvname) >= 5) {
8757 /* The stat01: line is from Crafty (9.29+) in response
8758 to the "." command */
8759 programStats.seen_stat = 1;
8760 cps->maybeThinking = TRUE;
8762 if (programStats.got_only_move || !appData.periodicUpdates)
8765 programStats.depth = plylev;
8766 programStats.time = time;
8767 programStats.nodes = nodes;
8768 programStats.moves_left = mvleft;
8769 programStats.nr_moves = mvtot;
8770 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8771 programStats.ok_to_send = 1;
8772 programStats.movelist[0] = '\0';
8774 SendProgramStatsToFrontend( cps, &programStats );
8778 } else if (strncmp(message,"++",2) == 0) {
8779 /* Crafty 9.29+ outputs this */
8780 programStats.got_fail = 2;
8783 } else if (strncmp(message,"--",2) == 0) {
8784 /* Crafty 9.29+ outputs this */
8785 programStats.got_fail = 1;
8788 } else if (thinkOutput[0] != NULLCHAR &&
8789 strncmp(message, " ", 4) == 0) {
8790 unsigned message_len;
8793 while (*p && *p == ' ') p++;
8795 message_len = strlen( p );
8797 /* [AS] Avoid buffer overflow */
8798 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8799 strcat(thinkOutput, " ");
8800 strcat(thinkOutput, p);
8803 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8804 strcat(programStats.movelist, " ");
8805 strcat(programStats.movelist, p);
8808 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8809 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8810 DisplayMove(currentMove - 1);
8818 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8819 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8821 ChessProgramStats cpstats;
8823 if (plyext != ' ' && plyext != '\t') {
8827 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8828 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8829 curscore = -curscore;
8832 cpstats.depth = plylev;
8833 cpstats.nodes = nodes;
8834 cpstats.time = time;
8835 cpstats.score = curscore;
8836 cpstats.got_only_move = 0;
8837 cpstats.movelist[0] = '\0';
8839 if (buf1[0] != NULLCHAR) {
8840 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8843 cpstats.ok_to_send = 0;
8844 cpstats.line_is_book = 0;
8845 cpstats.nr_moves = 0;
8846 cpstats.moves_left = 0;
8848 SendProgramStatsToFrontend( cps, &cpstats );
8855 /* Parse a game score from the character string "game", and
8856 record it as the history of the current game. The game
8857 score is NOT assumed to start from the standard position.
8858 The display is not updated in any way.
8861 ParseGameHistory(game)
8865 int fromX, fromY, toX, toY, boardIndex;
8870 if (appData.debugMode)
8871 fprintf(debugFP, "Parsing game history: %s\n", game);
8873 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8874 gameInfo.site = StrSave(appData.icsHost);
8875 gameInfo.date = PGNDate();
8876 gameInfo.round = StrSave("-");
8878 /* Parse out names of players */
8879 while (*game == ' ') game++;
8881 while (*game != ' ') *p++ = *game++;
8883 gameInfo.white = StrSave(buf);
8884 while (*game == ' ') game++;
8886 while (*game != ' ' && *game != '\n') *p++ = *game++;
8888 gameInfo.black = StrSave(buf);
8891 boardIndex = blackPlaysFirst ? 1 : 0;
8894 yyboardindex = boardIndex;
8895 moveType = (ChessMove) Myylex();
8897 case IllegalMove: /* maybe suicide chess, etc. */
8898 if (appData.debugMode) {
8899 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8900 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8901 setbuf(debugFP, NULL);
8903 case WhitePromotion:
8904 case BlackPromotion:
8905 case WhiteNonPromotion:
8906 case BlackNonPromotion:
8908 case WhiteCapturesEnPassant:
8909 case BlackCapturesEnPassant:
8910 case WhiteKingSideCastle:
8911 case WhiteQueenSideCastle:
8912 case BlackKingSideCastle:
8913 case BlackQueenSideCastle:
8914 case WhiteKingSideCastleWild:
8915 case WhiteQueenSideCastleWild:
8916 case BlackKingSideCastleWild:
8917 case BlackQueenSideCastleWild:
8919 case WhiteHSideCastleFR:
8920 case WhiteASideCastleFR:
8921 case BlackHSideCastleFR:
8922 case BlackASideCastleFR:
8924 fromX = currentMoveString[0] - AAA;
8925 fromY = currentMoveString[1] - ONE;
8926 toX = currentMoveString[2] - AAA;
8927 toY = currentMoveString[3] - ONE;
8928 promoChar = currentMoveString[4];
8932 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8933 fromX = moveType == WhiteDrop ?
8934 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8935 (int) CharToPiece(ToLower(currentMoveString[0]));
8937 toX = currentMoveString[2] - AAA;
8938 toY = currentMoveString[3] - ONE;
8939 promoChar = NULLCHAR;
8943 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8944 if (appData.debugMode) {
8945 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8946 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8947 setbuf(debugFP, NULL);
8949 DisplayError(buf, 0);
8951 case ImpossibleMove:
8953 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8954 if (appData.debugMode) {
8955 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8956 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8957 setbuf(debugFP, NULL);
8959 DisplayError(buf, 0);
8962 if (boardIndex < backwardMostMove) {
8963 /* Oops, gap. How did that happen? */
8964 DisplayError(_("Gap in move list"), 0);
8967 backwardMostMove = blackPlaysFirst ? 1 : 0;
8968 if (boardIndex > forwardMostMove) {
8969 forwardMostMove = boardIndex;
8973 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8974 strcat(parseList[boardIndex-1], " ");
8975 strcat(parseList[boardIndex-1], yy_text);
8987 case GameUnfinished:
8988 if (gameMode == IcsExamining) {
8989 if (boardIndex < backwardMostMove) {
8990 /* Oops, gap. How did that happen? */
8993 backwardMostMove = blackPlaysFirst ? 1 : 0;
8996 gameInfo.result = moveType;
8997 p = strchr(yy_text, '{');
8998 if (p == NULL) p = strchr(yy_text, '(');
9001 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9003 q = strchr(p, *p == '{' ? '}' : ')');
9004 if (q != NULL) *q = NULLCHAR;
9007 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9008 gameInfo.resultDetails = StrSave(p);
9011 if (boardIndex >= forwardMostMove &&
9012 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9013 backwardMostMove = blackPlaysFirst ? 1 : 0;
9016 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9017 fromY, fromX, toY, toX, promoChar,
9018 parseList[boardIndex]);
9019 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9020 /* currentMoveString is set as a side-effect of yylex */
9021 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9022 strcat(moveList[boardIndex], "\n");
9024 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9025 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9031 if(gameInfo.variant != VariantShogi)
9032 strcat(parseList[boardIndex - 1], "+");
9036 strcat(parseList[boardIndex - 1], "#");
9043 /* Apply a move to the given board */
9045 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9046 int fromX, fromY, toX, toY;
9050 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9051 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9053 /* [HGM] compute & store e.p. status and castling rights for new position */
9054 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9056 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9057 oldEP = (signed char)board[EP_STATUS];
9058 board[EP_STATUS] = EP_NONE;
9060 if (fromY == DROP_RANK) {
9062 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9063 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9066 piece = board[toY][toX] = (ChessSquare) fromX;
9070 if( board[toY][toX] != EmptySquare )
9071 board[EP_STATUS] = EP_CAPTURE;
9073 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9074 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9075 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9077 if( board[fromY][fromX] == WhitePawn ) {
9078 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9079 board[EP_STATUS] = EP_PAWN_MOVE;
9081 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9082 gameInfo.variant != VariantBerolina || toX < fromX)
9083 board[EP_STATUS] = toX | berolina;
9084 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9085 gameInfo.variant != VariantBerolina || toX > fromX)
9086 board[EP_STATUS] = toX;
9089 if( board[fromY][fromX] == BlackPawn ) {
9090 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9091 board[EP_STATUS] = EP_PAWN_MOVE;
9092 if( toY-fromY== -2) {
9093 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9094 gameInfo.variant != VariantBerolina || toX < fromX)
9095 board[EP_STATUS] = toX | berolina;
9096 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9097 gameInfo.variant != VariantBerolina || toX > fromX)
9098 board[EP_STATUS] = toX;
9102 for(i=0; i<nrCastlingRights; i++) {
9103 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9104 board[CASTLING][i] == toX && castlingRank[i] == toY
9105 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9108 if (fromX == toX && fromY == toY) return;
9110 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9111 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9112 if(gameInfo.variant == VariantKnightmate)
9113 king += (int) WhiteUnicorn - (int) WhiteKing;
9115 /* Code added by Tord: */
9116 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9117 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9118 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9119 board[fromY][fromX] = EmptySquare;
9120 board[toY][toX] = EmptySquare;
9121 if((toX > fromX) != (piece == WhiteRook)) {
9122 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9124 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9126 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9127 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9128 board[fromY][fromX] = EmptySquare;
9129 board[toY][toX] = EmptySquare;
9130 if((toX > fromX) != (piece == BlackRook)) {
9131 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9133 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9135 /* End of code added by Tord */
9137 } else if (board[fromY][fromX] == king
9138 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9139 && toY == fromY && toX > fromX+1) {
9140 board[fromY][fromX] = EmptySquare;
9141 board[toY][toX] = king;
9142 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9143 board[fromY][BOARD_RGHT-1] = EmptySquare;
9144 } else if (board[fromY][fromX] == king
9145 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9146 && toY == fromY && toX < fromX-1) {
9147 board[fromY][fromX] = EmptySquare;
9148 board[toY][toX] = king;
9149 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9150 board[fromY][BOARD_LEFT] = EmptySquare;
9151 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9152 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9153 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9155 /* white pawn promotion */
9156 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9157 if(gameInfo.variant==VariantBughouse ||
9158 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9159 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9160 board[fromY][fromX] = EmptySquare;
9161 } else if ((fromY >= BOARD_HEIGHT>>1)
9163 && gameInfo.variant != VariantXiangqi
9164 && gameInfo.variant != VariantBerolina
9165 && (board[fromY][fromX] == WhitePawn)
9166 && (board[toY][toX] == EmptySquare)) {
9167 board[fromY][fromX] = EmptySquare;
9168 board[toY][toX] = WhitePawn;
9169 captured = board[toY - 1][toX];
9170 board[toY - 1][toX] = EmptySquare;
9171 } else if ((fromY == BOARD_HEIGHT-4)
9173 && gameInfo.variant == VariantBerolina
9174 && (board[fromY][fromX] == WhitePawn)
9175 && (board[toY][toX] == EmptySquare)) {
9176 board[fromY][fromX] = EmptySquare;
9177 board[toY][toX] = WhitePawn;
9178 if(oldEP & EP_BEROLIN_A) {
9179 captured = board[fromY][fromX-1];
9180 board[fromY][fromX-1] = EmptySquare;
9181 }else{ captured = board[fromY][fromX+1];
9182 board[fromY][fromX+1] = EmptySquare;
9184 } else if (board[fromY][fromX] == king
9185 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9186 && toY == fromY && toX > fromX+1) {
9187 board[fromY][fromX] = EmptySquare;
9188 board[toY][toX] = king;
9189 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9190 board[fromY][BOARD_RGHT-1] = EmptySquare;
9191 } else if (board[fromY][fromX] == king
9192 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9193 && toY == fromY && toX < fromX-1) {
9194 board[fromY][fromX] = EmptySquare;
9195 board[toY][toX] = king;
9196 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9197 board[fromY][BOARD_LEFT] = EmptySquare;
9198 } else if (fromY == 7 && fromX == 3
9199 && board[fromY][fromX] == BlackKing
9200 && toY == 7 && toX == 5) {
9201 board[fromY][fromX] = EmptySquare;
9202 board[toY][toX] = BlackKing;
9203 board[fromY][7] = EmptySquare;
9204 board[toY][4] = BlackRook;
9205 } else if (fromY == 7 && fromX == 3
9206 && board[fromY][fromX] == BlackKing
9207 && toY == 7 && toX == 1) {
9208 board[fromY][fromX] = EmptySquare;
9209 board[toY][toX] = BlackKing;
9210 board[fromY][0] = EmptySquare;
9211 board[toY][2] = BlackRook;
9212 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9213 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9214 && toY < promoRank && promoChar
9216 /* black pawn promotion */
9217 board[toY][toX] = CharToPiece(ToLower(promoChar));
9218 if(gameInfo.variant==VariantBughouse ||
9219 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9220 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9221 board[fromY][fromX] = EmptySquare;
9222 } else if ((fromY < BOARD_HEIGHT>>1)
9224 && gameInfo.variant != VariantXiangqi
9225 && gameInfo.variant != VariantBerolina
9226 && (board[fromY][fromX] == BlackPawn)
9227 && (board[toY][toX] == EmptySquare)) {
9228 board[fromY][fromX] = EmptySquare;
9229 board[toY][toX] = BlackPawn;
9230 captured = board[toY + 1][toX];
9231 board[toY + 1][toX] = EmptySquare;
9232 } else if ((fromY == 3)
9234 && gameInfo.variant == VariantBerolina
9235 && (board[fromY][fromX] == BlackPawn)
9236 && (board[toY][toX] == EmptySquare)) {
9237 board[fromY][fromX] = EmptySquare;
9238 board[toY][toX] = BlackPawn;
9239 if(oldEP & EP_BEROLIN_A) {
9240 captured = board[fromY][fromX-1];
9241 board[fromY][fromX-1] = EmptySquare;
9242 }else{ captured = board[fromY][fromX+1];
9243 board[fromY][fromX+1] = EmptySquare;
9246 board[toY][toX] = board[fromY][fromX];
9247 board[fromY][fromX] = EmptySquare;
9251 if (gameInfo.holdingsWidth != 0) {
9253 /* !!A lot more code needs to be written to support holdings */
9254 /* [HGM] OK, so I have written it. Holdings are stored in the */
9255 /* penultimate board files, so they are automaticlly stored */
9256 /* in the game history. */
9257 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9258 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9259 /* Delete from holdings, by decreasing count */
9260 /* and erasing image if necessary */
9261 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9262 if(p < (int) BlackPawn) { /* white drop */
9263 p -= (int)WhitePawn;
9264 p = PieceToNumber((ChessSquare)p);
9265 if(p >= gameInfo.holdingsSize) p = 0;
9266 if(--board[p][BOARD_WIDTH-2] <= 0)
9267 board[p][BOARD_WIDTH-1] = EmptySquare;
9268 if((int)board[p][BOARD_WIDTH-2] < 0)
9269 board[p][BOARD_WIDTH-2] = 0;
9270 } else { /* black drop */
9271 p -= (int)BlackPawn;
9272 p = PieceToNumber((ChessSquare)p);
9273 if(p >= gameInfo.holdingsSize) p = 0;
9274 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9275 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9276 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9277 board[BOARD_HEIGHT-1-p][1] = 0;
9280 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9281 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9282 /* [HGM] holdings: Add to holdings, if holdings exist */
9283 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9284 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9285 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9288 if (p >= (int) BlackPawn) {
9289 p -= (int)BlackPawn;
9290 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9291 /* in Shogi restore piece to its original first */
9292 captured = (ChessSquare) (DEMOTED captured);
9295 p = PieceToNumber((ChessSquare)p);
9296 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9297 board[p][BOARD_WIDTH-2]++;
9298 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9300 p -= (int)WhitePawn;
9301 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9302 captured = (ChessSquare) (DEMOTED captured);
9305 p = PieceToNumber((ChessSquare)p);
9306 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9307 board[BOARD_HEIGHT-1-p][1]++;
9308 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9311 } else if (gameInfo.variant == VariantAtomic) {
9312 if (captured != EmptySquare) {
9314 for (y = toY-1; y <= toY+1; y++) {
9315 for (x = toX-1; x <= toX+1; x++) {
9316 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9317 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9318 board[y][x] = EmptySquare;
9322 board[toY][toX] = EmptySquare;
9325 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9326 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9328 if(promoChar == '+') {
9329 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9330 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9331 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9332 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9334 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9335 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9336 // [HGM] superchess: take promotion piece out of holdings
9337 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9338 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9339 if(!--board[k][BOARD_WIDTH-2])
9340 board[k][BOARD_WIDTH-1] = EmptySquare;
9342 if(!--board[BOARD_HEIGHT-1-k][1])
9343 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9349 /* Updates forwardMostMove */
9351 MakeMove(fromX, fromY, toX, toY, promoChar)
9352 int fromX, fromY, toX, toY;
9355 // forwardMostMove++; // [HGM] bare: moved downstream
9357 (void) CoordsToAlgebraic(boards[forwardMostMove],
9358 PosFlags(forwardMostMove),
9359 fromY, fromX, toY, toX, promoChar,
9360 parseList[forwardMostMove]);
9362 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9363 int timeLeft; static int lastLoadFlag=0; int king, piece;
9364 piece = boards[forwardMostMove][fromY][fromX];
9365 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9366 if(gameInfo.variant == VariantKnightmate)
9367 king += (int) WhiteUnicorn - (int) WhiteKing;
9368 if(forwardMostMove == 0) {
9369 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9370 fprintf(serverMoves, "%s;", UserName());
9371 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9372 fprintf(serverMoves, "%s;", second.tidy);
9373 fprintf(serverMoves, "%s;", first.tidy);
9374 if(gameMode == MachinePlaysWhite)
9375 fprintf(serverMoves, "%s;", UserName());
9376 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9377 fprintf(serverMoves, "%s;", second.tidy);
9378 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9379 lastLoadFlag = loadFlag;
9381 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9382 // print castling suffix
9383 if( toY == fromY && piece == king ) {
9385 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9387 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9390 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9391 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9392 boards[forwardMostMove][toY][toX] == EmptySquare
9393 && fromX != toX && fromY != toY)
9394 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9396 if(promoChar != NULLCHAR)
9397 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9399 char buf[MOVE_LEN*2], *p; int len;
9400 fprintf(serverMoves, "/%d/%d",
9401 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9402 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9403 else timeLeft = blackTimeRemaining/1000;
9404 fprintf(serverMoves, "/%d", timeLeft);
9405 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9406 if(p = strchr(buf, '=')) *p = NULLCHAR;
9407 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9408 fprintf(serverMoves, "/%s", buf);
9410 fflush(serverMoves);
9413 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9414 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9418 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9419 if (commentList[forwardMostMove+1] != NULL) {
9420 free(commentList[forwardMostMove+1]);
9421 commentList[forwardMostMove+1] = NULL;
9423 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9424 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9425 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9426 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9427 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9428 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9429 gameInfo.result = GameUnfinished;
9430 if (gameInfo.resultDetails != NULL) {
9431 free(gameInfo.resultDetails);
9432 gameInfo.resultDetails = NULL;
9434 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9435 moveList[forwardMostMove - 1]);
9436 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9442 if(gameInfo.variant != VariantShogi)
9443 strcat(parseList[forwardMostMove - 1], "+");
9447 strcat(parseList[forwardMostMove - 1], "#");
9450 if (appData.debugMode) {
9451 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9456 /* Updates currentMove if not pausing */
9458 ShowMove(fromX, fromY, toX, toY)
9460 int instant = (gameMode == PlayFromGameFile) ?
9461 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9462 if(appData.noGUI) return;
9463 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9465 if (forwardMostMove == currentMove + 1) {
9466 AnimateMove(boards[forwardMostMove - 1],
9467 fromX, fromY, toX, toY);
9469 if (appData.highlightLastMove) {
9470 SetHighlights(fromX, fromY, toX, toY);
9473 currentMove = forwardMostMove;
9476 if (instant) return;
9478 DisplayMove(currentMove - 1);
9479 DrawPosition(FALSE, boards[currentMove]);
9480 DisplayBothClocks();
9481 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9482 DisplayBook(currentMove);
9485 void SendEgtPath(ChessProgramState *cps)
9486 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9487 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9489 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9492 char c, *q = name+1, *r, *s;
9494 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9495 while(*p && *p != ',') *q++ = *p++;
9497 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9498 strcmp(name, ",nalimov:") == 0 ) {
9499 // take nalimov path from the menu-changeable option first, if it is defined
9500 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9501 SendToProgram(buf,cps); // send egtbpath command for nalimov
9503 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9504 (s = StrStr(appData.egtFormats, name)) != NULL) {
9505 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9506 s = r = StrStr(s, ":") + 1; // beginning of path info
9507 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9508 c = *r; *r = 0; // temporarily null-terminate path info
9509 *--q = 0; // strip of trailig ':' from name
9510 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9512 SendToProgram(buf,cps); // send egtbpath command for this format
9514 if(*p == ',') p++; // read away comma to position for next format name
9519 InitChessProgram(cps, setup)
9520 ChessProgramState *cps;
9521 int setup; /* [HGM] needed to setup FRC opening position */
9523 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9524 if (appData.noChessProgram) return;
9525 hintRequested = FALSE;
9526 bookRequested = FALSE;
9528 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9529 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9530 if(cps->memSize) { /* [HGM] memory */
9531 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9532 SendToProgram(buf, cps);
9534 SendEgtPath(cps); /* [HGM] EGT */
9535 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9536 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9537 SendToProgram(buf, cps);
9540 SendToProgram(cps->initString, cps);
9541 if (gameInfo.variant != VariantNormal &&
9542 gameInfo.variant != VariantLoadable
9543 /* [HGM] also send variant if board size non-standard */
9544 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9546 char *v = VariantName(gameInfo.variant);
9547 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9548 /* [HGM] in protocol 1 we have to assume all variants valid */
9549 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9550 DisplayFatalError(buf, 0, 1);
9554 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9555 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556 if( gameInfo.variant == VariantXiangqi )
9557 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9558 if( gameInfo.variant == VariantShogi )
9559 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9560 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9561 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9562 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9563 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9564 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9565 if( gameInfo.variant == VariantCourier )
9566 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567 if( gameInfo.variant == VariantSuper )
9568 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9569 if( gameInfo.variant == VariantGreat )
9570 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9571 if( gameInfo.variant == VariantSChess )
9572 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9573 if( gameInfo.variant == VariantGrand )
9574 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9577 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9578 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9579 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9580 if(StrStr(cps->variants, b) == NULL) {
9581 // specific sized variant not known, check if general sizing allowed
9582 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9583 if(StrStr(cps->variants, "boardsize") == NULL) {
9584 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9585 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9586 DisplayFatalError(buf, 0, 1);
9589 /* [HGM] here we really should compare with the maximum supported board size */
9592 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9593 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9594 SendToProgram(buf, cps);
9596 currentlyInitializedVariant = gameInfo.variant;
9598 /* [HGM] send opening position in FRC to first engine */
9600 SendToProgram("force\n", cps);
9602 /* engine is now in force mode! Set flag to wake it up after first move. */
9603 setboardSpoiledMachineBlack = 1;
9607 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9608 SendToProgram(buf, cps);
9610 cps->maybeThinking = FALSE;
9611 cps->offeredDraw = 0;
9612 if (!appData.icsActive) {
9613 SendTimeControl(cps, movesPerSession, timeControl,
9614 timeIncrement, appData.searchDepth,
9617 if (appData.showThinking
9618 // [HGM] thinking: four options require thinking output to be sent
9619 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9621 SendToProgram("post\n", cps);
9623 SendToProgram("hard\n", cps);
9624 if (!appData.ponderNextMove) {
9625 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9626 it without being sure what state we are in first. "hard"
9627 is not a toggle, so that one is OK.
9629 SendToProgram("easy\n", cps);
9632 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9633 SendToProgram(buf, cps);
9635 cps->initDone = TRUE;
9636 ClearEngineOutputPane(cps == &second);
9641 StartChessProgram(cps)
9642 ChessProgramState *cps;
9647 if (appData.noChessProgram) return;
9648 cps->initDone = FALSE;
9650 if (strcmp(cps->host, "localhost") == 0) {
9651 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9652 } else if (*appData.remoteShell == NULLCHAR) {
9653 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9655 if (*appData.remoteUser == NULLCHAR) {
9656 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9659 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9660 cps->host, appData.remoteUser, cps->program);
9662 err = StartChildProcess(buf, "", &cps->pr);
9666 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9667 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9668 if(cps != &first) return;
9669 appData.noChessProgram = TRUE;
9672 // DisplayFatalError(buf, err, 1);
9673 // cps->pr = NoProc;
9678 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9679 if (cps->protocolVersion > 1) {
9680 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9681 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9682 cps->comboCnt = 0; // and values of combo boxes
9683 SendToProgram(buf, cps);
9685 SendToProgram("xboard\n", cps);
9690 TwoMachinesEventIfReady P((void))
9692 static int curMess = 0;
9693 if (first.lastPing != first.lastPong) {
9694 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9695 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9698 if (second.lastPing != second.lastPong) {
9699 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9700 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9703 DisplayMessage("", ""); curMess = 0;
9709 MakeName(char *template)
9713 static char buf[MSG_SIZ];
9717 clock = time((time_t *)NULL);
9718 tm = localtime(&clock);
9720 while(*p++ = *template++) if(p[-1] == '%') {
9721 switch(*template++) {
9722 case 0: *p = 0; return buf;
9723 case 'Y': i = tm->tm_year+1900; break;
9724 case 'y': i = tm->tm_year-100; break;
9725 case 'M': i = tm->tm_mon+1; break;
9726 case 'd': i = tm->tm_mday; break;
9727 case 'h': i = tm->tm_hour; break;
9728 case 'm': i = tm->tm_min; break;
9729 case 's': i = tm->tm_sec; break;
9732 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9738 CountPlayers(char *p)
9741 while(p = strchr(p, '\n')) p++, n++; // count participants
9746 WriteTourneyFile(char *results, FILE *f)
9747 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9748 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9749 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9750 // create a file with tournament description
9751 fprintf(f, "-participants {%s}\n", appData.participants);
9752 fprintf(f, "-seedBase %d\n", appData.seedBase);
9753 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9754 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9755 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9756 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9757 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9758 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9759 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9760 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9761 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9762 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9763 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9765 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9767 fprintf(f, "-mps %d\n", appData.movesPerSession);
9768 fprintf(f, "-tc %s\n", appData.timeControl);
9769 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9771 fprintf(f, "-results \"%s\"\n", results);
9776 #define MAXENGINES 1000
9777 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9779 void Substitute(char *participants, int expunge)
9781 int i, changed, changes=0, nPlayers=0;
9782 char *p, *q, *r, buf[MSG_SIZ];
9783 if(participants == NULL) return;
9784 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9785 r = p = participants; q = appData.participants;
9786 while(*p && *p == *q) {
9787 if(*p == '\n') r = p+1, nPlayers++;
9790 if(*p) { // difference
9791 while(*p && *p++ != '\n');
9792 while(*q && *q++ != '\n');
9794 changes = 1 + (strcmp(p, q) != 0);
9796 if(changes == 1) { // a single engine mnemonic was changed
9797 q = r; while(*q) nPlayers += (*q++ == '\n');
9798 p = buf; while(*r && (*p = *r++) != '\n') p++;
9800 NamesToList(firstChessProgramNames, command, mnemonic);
9801 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9802 if(mnemonic[i]) { // The substitute is valid
9804 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9805 flock(fileno(f), LOCK_EX);
9806 ParseArgsFromFile(f);
9807 fseek(f, 0, SEEK_SET);
9808 FREE(appData.participants); appData.participants = participants;
9809 if(expunge) { // erase results of replaced engine
9810 int len = strlen(appData.results), w, b, dummy;
9811 for(i=0; i<len; i++) {
9812 Pairing(i, nPlayers, &w, &b, &dummy);
9813 if((w == changed || b == changed) && appData.results[i] == '*') {
9814 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9819 for(i=0; i<len; i++) {
9820 Pairing(i, nPlayers, &w, &b, &dummy);
9821 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9824 WriteTourneyFile(appData.results, f);
9825 fclose(f); // release lock
9828 } else DisplayError(_("No engine with the name you gave is installed"), 0);
9830 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9831 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
9837 CreateTourney(char *name)
9840 if(matchMode && strcmp(name, appData.tourneyFile)) {
9841 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9843 if(name[0] == NULLCHAR) {
9844 if(appData.participants[0])
9845 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9848 f = fopen(name, "r");
9849 if(f) { // file exists
9850 ASSIGN(appData.tourneyFile, name);
9851 ParseArgsFromFile(f); // parse it
9853 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9854 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9855 DisplayError(_("Not enough participants"), 0);
9858 ASSIGN(appData.tourneyFile, name);
9859 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9860 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9863 appData.noChessProgram = FALSE;
9864 appData.clockMode = TRUE;
9869 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9871 char buf[MSG_SIZ], *p, *q;
9875 while(*p && *p != '\n') *q++ = *p++;
9877 if(engineList[i]) free(engineList[i]);
9878 engineList[i] = strdup(buf);
9880 TidyProgramName(engineList[i], "localhost", buf);
9881 if(engineMnemonic[i]) free(engineMnemonic[i]);
9882 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9884 sscanf(q + 8, "%s", buf + strlen(buf));
9887 engineMnemonic[i] = strdup(buf);
9889 if(i > MAXENGINES - 2) break;
9891 engineList[i] = engineMnemonic[i] = NULL;
9894 // following implemented as macro to avoid type limitations
9895 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9897 void SwapEngines(int n)
9898 { // swap settings for first engine and other engine (so far only some selected options)
9903 SWAP(chessProgram, p)
9905 SWAP(hasOwnBookUCI, h)
9906 SWAP(protocolVersion, h)
9908 SWAP(scoreIsAbsolute, h)
9916 SetPlayer(int player)
9917 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9919 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9920 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9921 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9922 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9924 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9925 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9926 ParseArgsFromString(buf);
9932 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9933 { // determine players from game number
9934 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9936 if(appData.tourneyType == 0) {
9937 roundsPerCycle = (nPlayers - 1) | 1;
9938 pairingsPerRound = nPlayers / 2;
9939 } else if(appData.tourneyType > 0) {
9940 roundsPerCycle = nPlayers - appData.tourneyType;
9941 pairingsPerRound = appData.tourneyType;
9943 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9944 gamesPerCycle = gamesPerRound * roundsPerCycle;
9945 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9946 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9947 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9948 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9949 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9950 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9952 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9953 if(appData.roundSync) *syncInterval = gamesPerRound;
9955 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9957 if(appData.tourneyType == 0) {
9958 if(curPairing == (nPlayers-1)/2 ) {
9959 *whitePlayer = curRound;
9960 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9962 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9963 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9964 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9965 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9967 } else if(appData.tourneyType > 0) {
9968 *whitePlayer = curPairing;
9969 *blackPlayer = curRound + appData.tourneyType;
9972 // take care of white/black alternation per round.
9973 // For cycles and games this is already taken care of by default, derived from matchGame!
9974 return curRound & 1;
9978 NextTourneyGame(int nr, int *swapColors)
9979 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9981 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9983 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9984 tf = fopen(appData.tourneyFile, "r");
9985 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9986 ParseArgsFromFile(tf); fclose(tf);
9987 InitTimeControls(); // TC might be altered from tourney file
9989 nPlayers = CountPlayers(appData.participants); // count participants
9990 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9991 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9994 p = q = appData.results;
9995 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9996 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9997 DisplayMessage(_("Waiting for other game(s)"),"");
9998 waitingForGame = TRUE;
9999 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10002 waitingForGame = FALSE;
10005 if(appData.tourneyType < 0) {
10006 if(nr>=0 && !pairingReceived) {
10008 if(pairing.pr == NoProc) {
10009 if(!appData.pairingEngine[0]) {
10010 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10013 StartChessProgram(&pairing); // starts the pairing engine
10015 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10016 SendToProgram(buf, &pairing);
10017 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10018 SendToProgram(buf, &pairing);
10019 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10021 pairingReceived = 0; // ... so we continue here
10023 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10024 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10025 matchGame = 1; roundNr = nr / syncInterval + 1;
10028 if(first.pr != NoProc) return 1; // engines already loaded
10030 // redefine engines, engine dir, etc.
10031 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10032 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10034 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10035 SwapEngines(1); // and make that valid for second engine by swapping
10036 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10037 InitEngine(&second, 1);
10038 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10039 UpdateLogos(FALSE); // leave display to ModeHiglight()
10045 { // performs game initialization that does not invoke engines, and then tries to start the game
10046 int firstWhite, swapColors = 0;
10047 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10048 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10049 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10050 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10051 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10052 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10053 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10054 Reset(FALSE, first.pr != NoProc);
10055 appData.noChessProgram = FALSE;
10056 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
10057 TwoMachinesEvent();
10060 void UserAdjudicationEvent( int result )
10062 ChessMove gameResult = GameIsDrawn;
10065 gameResult = WhiteWins;
10067 else if( result < 0 ) {
10068 gameResult = BlackWins;
10071 if( gameMode == TwoMachinesPlay ) {
10072 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10077 // [HGM] save: calculate checksum of game to make games easily identifiable
10078 int StringCheckSum(char *s)
10081 if(s==NULL) return 0;
10082 while(*s) i = i*259 + *s++;
10089 for(i=backwardMostMove; i<forwardMostMove; i++) {
10090 sum += pvInfoList[i].depth;
10091 sum += StringCheckSum(parseList[i]);
10092 sum += StringCheckSum(commentList[i]);
10095 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10096 return sum + StringCheckSum(commentList[i]);
10097 } // end of save patch
10100 GameEnds(result, resultDetails, whosays)
10102 char *resultDetails;
10105 GameMode nextGameMode;
10107 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10109 if(endingGame) return; /* [HGM] crash: forbid recursion */
10111 if(twoBoards) { // [HGM] dual: switch back to one board
10112 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10113 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10115 if (appData.debugMode) {
10116 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10117 result, resultDetails ? resultDetails : "(null)", whosays);
10120 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10122 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10123 /* If we are playing on ICS, the server decides when the
10124 game is over, but the engine can offer to draw, claim
10128 if (appData.zippyPlay && first.initDone) {
10129 if (result == GameIsDrawn) {
10130 /* In case draw still needs to be claimed */
10131 SendToICS(ics_prefix);
10132 SendToICS("draw\n");
10133 } else if (StrCaseStr(resultDetails, "resign")) {
10134 SendToICS(ics_prefix);
10135 SendToICS("resign\n");
10139 endingGame = 0; /* [HGM] crash */
10143 /* If we're loading the game from a file, stop */
10144 if (whosays == GE_FILE) {
10145 (void) StopLoadGameTimer();
10149 /* Cancel draw offers */
10150 first.offeredDraw = second.offeredDraw = 0;
10152 /* If this is an ICS game, only ICS can really say it's done;
10153 if not, anyone can. */
10154 isIcsGame = (gameMode == IcsPlayingWhite ||
10155 gameMode == IcsPlayingBlack ||
10156 gameMode == IcsObserving ||
10157 gameMode == IcsExamining);
10159 if (!isIcsGame || whosays == GE_ICS) {
10160 /* OK -- not an ICS game, or ICS said it was done */
10162 if (!isIcsGame && !appData.noChessProgram)
10163 SetUserThinkingEnables();
10165 /* [HGM] if a machine claims the game end we verify this claim */
10166 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10167 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10169 ChessMove trueResult = (ChessMove) -1;
10171 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10172 first.twoMachinesColor[0] :
10173 second.twoMachinesColor[0] ;
10175 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10176 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10177 /* [HGM] verify: engine mate claims accepted if they were flagged */
10178 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10180 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10181 /* [HGM] verify: engine mate claims accepted if they were flagged */
10182 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10184 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10185 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10188 // now verify win claims, but not in drop games, as we don't understand those yet
10189 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10190 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10191 (result == WhiteWins && claimer == 'w' ||
10192 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10193 if (appData.debugMode) {
10194 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10195 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10197 if(result != trueResult) {
10198 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10199 result = claimer == 'w' ? BlackWins : WhiteWins;
10200 resultDetails = buf;
10203 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10204 && (forwardMostMove <= backwardMostMove ||
10205 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10206 (claimer=='b')==(forwardMostMove&1))
10208 /* [HGM] verify: draws that were not flagged are false claims */
10209 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10210 result = claimer == 'w' ? BlackWins : WhiteWins;
10211 resultDetails = buf;
10213 /* (Claiming a loss is accepted no questions asked!) */
10215 /* [HGM] bare: don't allow bare King to win */
10216 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10217 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10218 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10219 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10220 && result != GameIsDrawn)
10221 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10222 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10223 int p = (signed char)boards[forwardMostMove][i][j] - color;
10224 if(p >= 0 && p <= (int)WhiteKing) k++;
10226 if (appData.debugMode) {
10227 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10228 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10231 result = GameIsDrawn;
10232 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10233 resultDetails = buf;
10239 if(serverMoves != NULL && !loadFlag) { char c = '=';
10240 if(result==WhiteWins) c = '+';
10241 if(result==BlackWins) c = '-';
10242 if(resultDetails != NULL)
10243 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10245 if (resultDetails != NULL) {
10246 gameInfo.result = result;
10247 gameInfo.resultDetails = StrSave(resultDetails);
10249 /* display last move only if game was not loaded from file */
10250 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10251 DisplayMove(currentMove - 1);
10253 if (forwardMostMove != 0) {
10254 if (gameMode != PlayFromGameFile && gameMode != EditGame
10255 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10257 if (*appData.saveGameFile != NULLCHAR) {
10258 SaveGameToFile(appData.saveGameFile, TRUE);
10259 } else if (appData.autoSaveGames) {
10262 if (*appData.savePositionFile != NULLCHAR) {
10263 SavePositionToFile(appData.savePositionFile);
10268 /* Tell program how game ended in case it is learning */
10269 /* [HGM] Moved this to after saving the PGN, just in case */
10270 /* engine died and we got here through time loss. In that */
10271 /* case we will get a fatal error writing the pipe, which */
10272 /* would otherwise lose us the PGN. */
10273 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10274 /* output during GameEnds should never be fatal anymore */
10275 if (gameMode == MachinePlaysWhite ||
10276 gameMode == MachinePlaysBlack ||
10277 gameMode == TwoMachinesPlay ||
10278 gameMode == IcsPlayingWhite ||
10279 gameMode == IcsPlayingBlack ||
10280 gameMode == BeginningOfGame) {
10282 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10284 if (first.pr != NoProc) {
10285 SendToProgram(buf, &first);
10287 if (second.pr != NoProc &&
10288 gameMode == TwoMachinesPlay) {
10289 SendToProgram(buf, &second);
10294 if (appData.icsActive) {
10295 if (appData.quietPlay &&
10296 (gameMode == IcsPlayingWhite ||
10297 gameMode == IcsPlayingBlack)) {
10298 SendToICS(ics_prefix);
10299 SendToICS("set shout 1\n");
10301 nextGameMode = IcsIdle;
10302 ics_user_moved = FALSE;
10303 /* clean up premove. It's ugly when the game has ended and the
10304 * premove highlights are still on the board.
10307 gotPremove = FALSE;
10308 ClearPremoveHighlights();
10309 DrawPosition(FALSE, boards[currentMove]);
10311 if (whosays == GE_ICS) {
10314 if (gameMode == IcsPlayingWhite)
10316 else if(gameMode == IcsPlayingBlack)
10317 PlayIcsLossSound();
10320 if (gameMode == IcsPlayingBlack)
10322 else if(gameMode == IcsPlayingWhite)
10323 PlayIcsLossSound();
10326 PlayIcsDrawSound();
10329 PlayIcsUnfinishedSound();
10332 } else if (gameMode == EditGame ||
10333 gameMode == PlayFromGameFile ||
10334 gameMode == AnalyzeMode ||
10335 gameMode == AnalyzeFile) {
10336 nextGameMode = gameMode;
10338 nextGameMode = EndOfGame;
10343 nextGameMode = gameMode;
10346 if (appData.noChessProgram) {
10347 gameMode = nextGameMode;
10349 endingGame = 0; /* [HGM] crash */
10354 /* Put first chess program into idle state */
10355 if (first.pr != NoProc &&
10356 (gameMode == MachinePlaysWhite ||
10357 gameMode == MachinePlaysBlack ||
10358 gameMode == TwoMachinesPlay ||
10359 gameMode == IcsPlayingWhite ||
10360 gameMode == IcsPlayingBlack ||
10361 gameMode == BeginningOfGame)) {
10362 SendToProgram("force\n", &first);
10363 if (first.usePing) {
10365 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10366 SendToProgram(buf, &first);
10369 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10370 /* Kill off first chess program */
10371 if (first.isr != NULL)
10372 RemoveInputSource(first.isr);
10375 if (first.pr != NoProc) {
10377 DoSleep( appData.delayBeforeQuit );
10378 SendToProgram("quit\n", &first);
10379 DoSleep( appData.delayAfterQuit );
10380 DestroyChildProcess(first.pr, first.useSigterm);
10384 if (second.reuse) {
10385 /* Put second chess program into idle state */
10386 if (second.pr != NoProc &&
10387 gameMode == TwoMachinesPlay) {
10388 SendToProgram("force\n", &second);
10389 if (second.usePing) {
10391 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10392 SendToProgram(buf, &second);
10395 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10396 /* Kill off second chess program */
10397 if (second.isr != NULL)
10398 RemoveInputSource(second.isr);
10401 if (second.pr != NoProc) {
10402 DoSleep( appData.delayBeforeQuit );
10403 SendToProgram("quit\n", &second);
10404 DoSleep( appData.delayAfterQuit );
10405 DestroyChildProcess(second.pr, second.useSigterm);
10407 second.pr = NoProc;
10410 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10411 char resChar = '=';
10415 if (first.twoMachinesColor[0] == 'w') {
10418 second.matchWins++;
10423 if (first.twoMachinesColor[0] == 'b') {
10426 second.matchWins++;
10429 case GameUnfinished:
10435 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10436 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10437 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10438 ReserveGame(nextGame, resChar); // sets nextGame
10439 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10440 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10441 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10443 if (nextGame <= appData.matchGames && !abortMatch) {
10444 gameMode = nextGameMode;
10445 matchGame = nextGame; // this will be overruled in tourney mode!
10446 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10447 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10448 endingGame = 0; /* [HGM] crash */
10451 gameMode = nextGameMode;
10452 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10453 first.tidy, second.tidy,
10454 first.matchWins, second.matchWins,
10455 appData.matchGames - (first.matchWins + second.matchWins));
10456 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10457 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10458 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10459 first.twoMachinesColor = "black\n";
10460 second.twoMachinesColor = "white\n";
10462 first.twoMachinesColor = "white\n";
10463 second.twoMachinesColor = "black\n";
10467 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10468 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10470 gameMode = nextGameMode;
10472 endingGame = 0; /* [HGM] crash */
10473 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10474 if(matchMode == TRUE) { // match through command line: exit with or without popup
10476 ToNrEvent(forwardMostMove);
10477 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10479 } else DisplayFatalError(buf, 0, 0);
10480 } else { // match through menu; just stop, with or without popup
10481 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10484 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10485 } else DisplayNote(buf);
10487 if(ranking) free(ranking);
10491 /* Assumes program was just initialized (initString sent).
10492 Leaves program in force mode. */
10494 FeedMovesToProgram(cps, upto)
10495 ChessProgramState *cps;
10500 if (appData.debugMode)
10501 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10502 startedFromSetupPosition ? "position and " : "",
10503 backwardMostMove, upto, cps->which);
10504 if(currentlyInitializedVariant != gameInfo.variant) {
10506 // [HGM] variantswitch: make engine aware of new variant
10507 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10508 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10509 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10510 SendToProgram(buf, cps);
10511 currentlyInitializedVariant = gameInfo.variant;
10513 SendToProgram("force\n", cps);
10514 if (startedFromSetupPosition) {
10515 SendBoard(cps, backwardMostMove);
10516 if (appData.debugMode) {
10517 fprintf(debugFP, "feedMoves\n");
10520 for (i = backwardMostMove; i < upto; i++) {
10521 SendMoveToProgram(i, cps);
10527 ResurrectChessProgram()
10529 /* The chess program may have exited.
10530 If so, restart it and feed it all the moves made so far. */
10531 static int doInit = 0;
10533 if (appData.noChessProgram) return 1;
10535 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10536 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10537 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10538 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10540 if (first.pr != NoProc) return 1;
10541 StartChessProgram(&first);
10543 InitChessProgram(&first, FALSE);
10544 FeedMovesToProgram(&first, currentMove);
10546 if (!first.sendTime) {
10547 /* can't tell gnuchess what its clock should read,
10548 so we bow to its notion. */
10550 timeRemaining[0][currentMove] = whiteTimeRemaining;
10551 timeRemaining[1][currentMove] = blackTimeRemaining;
10554 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10555 appData.icsEngineAnalyze) && first.analysisSupport) {
10556 SendToProgram("analyze\n", &first);
10557 first.analyzing = TRUE;
10563 * Button procedures
10566 Reset(redraw, init)
10571 if (appData.debugMode) {
10572 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10573 redraw, init, gameMode);
10575 CleanupTail(); // [HGM] vari: delete any stored variations
10576 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10577 pausing = pauseExamInvalid = FALSE;
10578 startedFromSetupPosition = blackPlaysFirst = FALSE;
10580 whiteFlag = blackFlag = FALSE;
10581 userOfferedDraw = FALSE;
10582 hintRequested = bookRequested = FALSE;
10583 first.maybeThinking = FALSE;
10584 second.maybeThinking = FALSE;
10585 first.bookSuspend = FALSE; // [HGM] book
10586 second.bookSuspend = FALSE;
10587 thinkOutput[0] = NULLCHAR;
10588 lastHint[0] = NULLCHAR;
10589 ClearGameInfo(&gameInfo);
10590 gameInfo.variant = StringToVariant(appData.variant);
10591 ics_user_moved = ics_clock_paused = FALSE;
10592 ics_getting_history = H_FALSE;
10594 white_holding[0] = black_holding[0] = NULLCHAR;
10595 ClearProgramStats();
10596 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10600 flipView = appData.flipView;
10601 ClearPremoveHighlights();
10602 gotPremove = FALSE;
10603 alarmSounded = FALSE;
10605 GameEnds(EndOfFile, NULL, GE_PLAYER);
10606 if(appData.serverMovesName != NULL) {
10607 /* [HGM] prepare to make moves file for broadcasting */
10608 clock_t t = clock();
10609 if(serverMoves != NULL) fclose(serverMoves);
10610 serverMoves = fopen(appData.serverMovesName, "r");
10611 if(serverMoves != NULL) {
10612 fclose(serverMoves);
10613 /* delay 15 sec before overwriting, so all clients can see end */
10614 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10616 serverMoves = fopen(appData.serverMovesName, "w");
10620 gameMode = BeginningOfGame;
10622 if(appData.icsActive) gameInfo.variant = VariantNormal;
10623 currentMove = forwardMostMove = backwardMostMove = 0;
10624 InitPosition(redraw);
10625 for (i = 0; i < MAX_MOVES; i++) {
10626 if (commentList[i] != NULL) {
10627 free(commentList[i]);
10628 commentList[i] = NULL;
10632 timeRemaining[0][0] = whiteTimeRemaining;
10633 timeRemaining[1][0] = blackTimeRemaining;
10635 if (first.pr == NULL) {
10636 StartChessProgram(&first);
10639 InitChessProgram(&first, startedFromSetupPosition);
10642 DisplayMessage("", "");
10643 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10644 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10651 if (!AutoPlayOneMove())
10653 if (matchMode || appData.timeDelay == 0)
10655 if (appData.timeDelay < 0)
10657 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10666 int fromX, fromY, toX, toY;
10668 if (appData.debugMode) {
10669 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10672 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10675 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10676 pvInfoList[currentMove].depth = programStats.depth;
10677 pvInfoList[currentMove].score = programStats.score;
10678 pvInfoList[currentMove].time = 0;
10679 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10682 if (currentMove >= forwardMostMove) {
10683 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10684 // gameMode = EndOfGame;
10685 // ModeHighlight();
10687 /* [AS] Clear current move marker at the end of a game */
10688 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10693 toX = moveList[currentMove][2] - AAA;
10694 toY = moveList[currentMove][3] - ONE;
10696 if (moveList[currentMove][1] == '@') {
10697 if (appData.highlightLastMove) {
10698 SetHighlights(-1, -1, toX, toY);
10701 fromX = moveList[currentMove][0] - AAA;
10702 fromY = moveList[currentMove][1] - ONE;
10704 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10706 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10708 if (appData.highlightLastMove) {
10709 SetHighlights(fromX, fromY, toX, toY);
10712 DisplayMove(currentMove);
10713 SendMoveToProgram(currentMove++, &first);
10714 DisplayBothClocks();
10715 DrawPosition(FALSE, boards[currentMove]);
10716 // [HGM] PV info: always display, routine tests if empty
10717 DisplayComment(currentMove - 1, commentList[currentMove]);
10723 LoadGameOneMove(readAhead)
10724 ChessMove readAhead;
10726 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10727 char promoChar = NULLCHAR;
10728 ChessMove moveType;
10729 char move[MSG_SIZ];
10732 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10733 gameMode != AnalyzeMode && gameMode != Training) {
10738 yyboardindex = forwardMostMove;
10739 if (readAhead != EndOfFile) {
10740 moveType = readAhead;
10742 if (gameFileFP == NULL)
10744 moveType = (ChessMove) Myylex();
10748 switch (moveType) {
10750 if (appData.debugMode)
10751 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10754 /* append the comment but don't display it */
10755 AppendComment(currentMove, p, FALSE);
10758 case WhiteCapturesEnPassant:
10759 case BlackCapturesEnPassant:
10760 case WhitePromotion:
10761 case BlackPromotion:
10762 case WhiteNonPromotion:
10763 case BlackNonPromotion:
10765 case WhiteKingSideCastle:
10766 case WhiteQueenSideCastle:
10767 case BlackKingSideCastle:
10768 case BlackQueenSideCastle:
10769 case WhiteKingSideCastleWild:
10770 case WhiteQueenSideCastleWild:
10771 case BlackKingSideCastleWild:
10772 case BlackQueenSideCastleWild:
10774 case WhiteHSideCastleFR:
10775 case WhiteASideCastleFR:
10776 case BlackHSideCastleFR:
10777 case BlackASideCastleFR:
10779 if (appData.debugMode)
10780 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10781 fromX = currentMoveString[0] - AAA;
10782 fromY = currentMoveString[1] - ONE;
10783 toX = currentMoveString[2] - AAA;
10784 toY = currentMoveString[3] - ONE;
10785 promoChar = currentMoveString[4];
10790 if (appData.debugMode)
10791 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10792 fromX = moveType == WhiteDrop ?
10793 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10794 (int) CharToPiece(ToLower(currentMoveString[0]));
10796 toX = currentMoveString[2] - AAA;
10797 toY = currentMoveString[3] - ONE;
10803 case GameUnfinished:
10804 if (appData.debugMode)
10805 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10806 p = strchr(yy_text, '{');
10807 if (p == NULL) p = strchr(yy_text, '(');
10810 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10812 q = strchr(p, *p == '{' ? '}' : ')');
10813 if (q != NULL) *q = NULLCHAR;
10816 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10817 GameEnds(moveType, p, GE_FILE);
10819 if (cmailMsgLoaded) {
10821 flipView = WhiteOnMove(currentMove);
10822 if (moveType == GameUnfinished) flipView = !flipView;
10823 if (appData.debugMode)
10824 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10829 if (appData.debugMode)
10830 fprintf(debugFP, "Parser hit end of file\n");
10831 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10837 if (WhiteOnMove(currentMove)) {
10838 GameEnds(BlackWins, "Black mates", GE_FILE);
10840 GameEnds(WhiteWins, "White mates", GE_FILE);
10844 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10850 case MoveNumberOne:
10851 if (lastLoadGameStart == GNUChessGame) {
10852 /* GNUChessGames have numbers, but they aren't move numbers */
10853 if (appData.debugMode)
10854 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10855 yy_text, (int) moveType);
10856 return LoadGameOneMove(EndOfFile); /* tail recursion */
10858 /* else fall thru */
10863 /* Reached start of next game in file */
10864 if (appData.debugMode)
10865 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10866 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10872 if (WhiteOnMove(currentMove)) {
10873 GameEnds(BlackWins, "Black mates", GE_FILE);
10875 GameEnds(WhiteWins, "White mates", GE_FILE);
10879 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10885 case PositionDiagram: /* should not happen; ignore */
10886 case ElapsedTime: /* ignore */
10887 case NAG: /* ignore */
10888 if (appData.debugMode)
10889 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10890 yy_text, (int) moveType);
10891 return LoadGameOneMove(EndOfFile); /* tail recursion */
10894 if (appData.testLegality) {
10895 if (appData.debugMode)
10896 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10897 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10898 (forwardMostMove / 2) + 1,
10899 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10900 DisplayError(move, 0);
10903 if (appData.debugMode)
10904 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10905 yy_text, currentMoveString);
10906 fromX = currentMoveString[0] - AAA;
10907 fromY = currentMoveString[1] - ONE;
10908 toX = currentMoveString[2] - AAA;
10909 toY = currentMoveString[3] - ONE;
10910 promoChar = currentMoveString[4];
10914 case AmbiguousMove:
10915 if (appData.debugMode)
10916 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10917 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10918 (forwardMostMove / 2) + 1,
10919 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920 DisplayError(move, 0);
10925 case ImpossibleMove:
10926 if (appData.debugMode)
10927 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10928 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10929 (forwardMostMove / 2) + 1,
10930 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10931 DisplayError(move, 0);
10937 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10938 DrawPosition(FALSE, boards[currentMove]);
10939 DisplayBothClocks();
10940 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10941 DisplayComment(currentMove - 1, commentList[currentMove]);
10943 (void) StopLoadGameTimer();
10945 cmailOldMove = forwardMostMove;
10948 /* currentMoveString is set as a side-effect of yylex */
10950 thinkOutput[0] = NULLCHAR;
10951 MakeMove(fromX, fromY, toX, toY, promoChar);
10952 currentMove = forwardMostMove;
10957 /* Load the nth game from the given file */
10959 LoadGameFromFile(filename, n, title, useList)
10963 /*Boolean*/ int useList;
10968 if (strcmp(filename, "-") == 0) {
10972 f = fopen(filename, "rb");
10974 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10975 DisplayError(buf, errno);
10979 if (fseek(f, 0, 0) == -1) {
10980 /* f is not seekable; probably a pipe */
10983 if (useList && n == 0) {
10984 int error = GameListBuild(f);
10986 DisplayError(_("Cannot build game list"), error);
10987 } else if (!ListEmpty(&gameList) &&
10988 ((ListGame *) gameList.tailPred)->number > 1) {
10989 GameListPopUp(f, title);
10996 return LoadGame(f, n, title, FALSE);
11001 MakeRegisteredMove()
11003 int fromX, fromY, toX, toY;
11005 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11006 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11009 if (appData.debugMode)
11010 fprintf(debugFP, "Restoring %s for game %d\n",
11011 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11013 thinkOutput[0] = NULLCHAR;
11014 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11015 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11016 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11017 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11018 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11019 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11020 MakeMove(fromX, fromY, toX, toY, promoChar);
11021 ShowMove(fromX, fromY, toX, toY);
11023 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11030 if (WhiteOnMove(currentMove)) {
11031 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11033 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11038 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11045 if (WhiteOnMove(currentMove)) {
11046 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11048 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11053 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11064 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11066 CmailLoadGame(f, gameNumber, title, useList)
11074 if (gameNumber > nCmailGames) {
11075 DisplayError(_("No more games in this message"), 0);
11078 if (f == lastLoadGameFP) {
11079 int offset = gameNumber - lastLoadGameNumber;
11081 cmailMsg[0] = NULLCHAR;
11082 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11083 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11084 nCmailMovesRegistered--;
11086 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11087 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11088 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11091 if (! RegisterMove()) return FALSE;
11095 retVal = LoadGame(f, gameNumber, title, useList);
11097 /* Make move registered during previous look at this game, if any */
11098 MakeRegisteredMove();
11100 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11101 commentList[currentMove]
11102 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11103 DisplayComment(currentMove - 1, commentList[currentMove]);
11109 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11114 int gameNumber = lastLoadGameNumber + offset;
11115 if (lastLoadGameFP == NULL) {
11116 DisplayError(_("No game has been loaded yet"), 0);
11119 if (gameNumber <= 0) {
11120 DisplayError(_("Can't back up any further"), 0);
11123 if (cmailMsgLoaded) {
11124 return CmailLoadGame(lastLoadGameFP, gameNumber,
11125 lastLoadGameTitle, lastLoadGameUseList);
11127 return LoadGame(lastLoadGameFP, gameNumber,
11128 lastLoadGameTitle, lastLoadGameUseList);
11132 int keys[EmptySquare+1];
11135 PositionMatches(Board b1, Board b2)
11138 switch(appData.searchMode) {
11139 case 1: return CompareWithRights(b1, b2);
11141 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11142 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11146 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11147 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11148 sum += keys[b1[r][f]] - keys[b2[r][f]];
11152 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11153 sum += keys[b1[r][f]] - keys[b2[r][f]];
11160 GameInfo dummyInfo;
11162 int GameContainsPosition(FILE *f, ListGame *lg)
11164 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11165 int fromX, fromY, toX, toY;
11167 static int initDone=FALSE;
11170 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11173 dummyInfo.variant = VariantNormal;
11174 FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11175 dummyInfo.whiteRating = 0;
11176 dummyInfo.blackRating = 0;
11177 FREE(dummyInfo.date); dummyInfo.date = NULL;
11178 fseek(f, lg->offset, 0);
11180 CopyBoard(boards[scratch], initialPosition); // default start position
11182 yyboardindex = scratch + (plyNr&1);
11188 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11190 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11191 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11193 // do it ourselves avoiding malloc
11194 { char *p = yy_text+1, *q;
11195 while(!isdigit(*p) && !isalpha(*p)) p++;
11196 q = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11198 if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11199 if(!StrCaseCmp(q, "Variant") && (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11200 if(!StrCaseCmp(q, "WhiteElo") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11201 if(!StrCaseCmp(q, "BlackElo") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11202 if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11203 if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11204 if(!StrCaseCmp(q, "FEN") && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11212 if(plyNr) return -1; // after we have seen moves, this is for new game
11215 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11216 case ImpossibleMove:
11217 case WhiteWins: // game ends here with these four
11220 case GameUnfinished:
11224 if(appData.testLegality) return -1;
11225 case WhiteCapturesEnPassant:
11226 case BlackCapturesEnPassant:
11227 case WhitePromotion:
11228 case BlackPromotion:
11229 case WhiteNonPromotion:
11230 case BlackNonPromotion:
11232 case WhiteKingSideCastle:
11233 case WhiteQueenSideCastle:
11234 case BlackKingSideCastle:
11235 case BlackQueenSideCastle:
11236 case WhiteKingSideCastleWild:
11237 case WhiteQueenSideCastleWild:
11238 case BlackKingSideCastleWild:
11239 case BlackQueenSideCastleWild:
11240 case WhiteHSideCastleFR:
11241 case WhiteASideCastleFR:
11242 case BlackHSideCastleFR:
11243 case BlackASideCastleFR:
11244 fromX = currentMoveString[0] - AAA;
11245 fromY = currentMoveString[1] - ONE;
11246 toX = currentMoveString[2] - AAA;
11247 toY = currentMoveString[3] - ONE;
11248 promoChar = currentMoveString[4];
11252 fromX = next == WhiteDrop ?
11253 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11254 (int) CharToPiece(ToLower(currentMoveString[0]));
11256 toX = currentMoveString[2] - AAA;
11257 toY = currentMoveString[3] - ONE;
11260 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11261 if(plyNr == 0) { // but first figure out variant and initial position
11262 if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11263 if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11264 if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11265 if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11266 if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11267 if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11269 CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11271 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11272 if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11276 /* Load the nth game from open file f */
11278 LoadGame(f, gameNumber, title, useList)
11286 int gn = gameNumber;
11287 ListGame *lg = NULL;
11288 int numPGNTags = 0;
11290 GameMode oldGameMode;
11291 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11293 if (appData.debugMode)
11294 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11296 if (gameMode == Training )
11297 SetTrainingModeOff();
11299 oldGameMode = gameMode;
11300 if (gameMode != BeginningOfGame) {
11301 Reset(FALSE, TRUE);
11305 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11306 fclose(lastLoadGameFP);
11310 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11313 fseek(f, lg->offset, 0);
11314 GameListHighlight(gameNumber);
11315 pos = lg->position;
11319 DisplayError(_("Game number out of range"), 0);
11324 if (fseek(f, 0, 0) == -1) {
11325 if (f == lastLoadGameFP ?
11326 gameNumber == lastLoadGameNumber + 1 :
11330 DisplayError(_("Can't seek on game file"), 0);
11335 lastLoadGameFP = f;
11336 lastLoadGameNumber = gameNumber;
11337 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11338 lastLoadGameUseList = useList;
11342 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11343 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11344 lg->gameInfo.black);
11346 } else if (*title != NULLCHAR) {
11347 if (gameNumber > 1) {
11348 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11351 DisplayTitle(title);
11355 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11356 gameMode = PlayFromGameFile;
11360 currentMove = forwardMostMove = backwardMostMove = 0;
11361 CopyBoard(boards[0], initialPosition);
11365 * Skip the first gn-1 games in the file.
11366 * Also skip over anything that precedes an identifiable
11367 * start of game marker, to avoid being confused by
11368 * garbage at the start of the file. Currently
11369 * recognized start of game markers are the move number "1",
11370 * the pattern "gnuchess .* game", the pattern
11371 * "^[#;%] [^ ]* game file", and a PGN tag block.
11372 * A game that starts with one of the latter two patterns
11373 * will also have a move number 1, possibly
11374 * following a position diagram.
11375 * 5-4-02: Let's try being more lenient and allowing a game to
11376 * start with an unnumbered move. Does that break anything?
11378 cm = lastLoadGameStart = EndOfFile;
11380 yyboardindex = forwardMostMove;
11381 cm = (ChessMove) Myylex();
11384 if (cmailMsgLoaded) {
11385 nCmailGames = CMAIL_MAX_GAMES - gn;
11388 DisplayError(_("Game not found in file"), 0);
11395 lastLoadGameStart = cm;
11398 case MoveNumberOne:
11399 switch (lastLoadGameStart) {
11404 case MoveNumberOne:
11406 gn--; /* count this game */
11407 lastLoadGameStart = cm;
11416 switch (lastLoadGameStart) {
11419 case MoveNumberOne:
11421 gn--; /* count this game */
11422 lastLoadGameStart = cm;
11425 lastLoadGameStart = cm; /* game counted already */
11433 yyboardindex = forwardMostMove;
11434 cm = (ChessMove) Myylex();
11435 } while (cm == PGNTag || cm == Comment);
11442 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11443 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11444 != CMAIL_OLD_RESULT) {
11446 cmailResult[ CMAIL_MAX_GAMES
11447 - gn - 1] = CMAIL_OLD_RESULT;
11453 /* Only a NormalMove can be at the start of a game
11454 * without a position diagram. */
11455 if (lastLoadGameStart == EndOfFile ) {
11457 lastLoadGameStart = MoveNumberOne;
11466 if (appData.debugMode)
11467 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11469 if (cm == XBoardGame) {
11470 /* Skip any header junk before position diagram and/or move 1 */
11472 yyboardindex = forwardMostMove;
11473 cm = (ChessMove) Myylex();
11475 if (cm == EndOfFile ||
11476 cm == GNUChessGame || cm == XBoardGame) {
11477 /* Empty game; pretend end-of-file and handle later */
11482 if (cm == MoveNumberOne || cm == PositionDiagram ||
11483 cm == PGNTag || cm == Comment)
11486 } else if (cm == GNUChessGame) {
11487 if (gameInfo.event != NULL) {
11488 free(gameInfo.event);
11490 gameInfo.event = StrSave(yy_text);
11493 startedFromSetupPosition = FALSE;
11494 while (cm == PGNTag) {
11495 if (appData.debugMode)
11496 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11497 err = ParsePGNTag(yy_text, &gameInfo);
11498 if (!err) numPGNTags++;
11500 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11501 if(gameInfo.variant != oldVariant) {
11502 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11503 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11504 InitPosition(TRUE);
11505 oldVariant = gameInfo.variant;
11506 if (appData.debugMode)
11507 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11511 if (gameInfo.fen != NULL) {
11512 Board initial_position;
11513 startedFromSetupPosition = TRUE;
11514 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11516 DisplayError(_("Bad FEN position in file"), 0);
11519 CopyBoard(boards[0], initial_position);
11520 if (blackPlaysFirst) {
11521 currentMove = forwardMostMove = backwardMostMove = 1;
11522 CopyBoard(boards[1], initial_position);
11523 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11524 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11525 timeRemaining[0][1] = whiteTimeRemaining;
11526 timeRemaining[1][1] = blackTimeRemaining;
11527 if (commentList[0] != NULL) {
11528 commentList[1] = commentList[0];
11529 commentList[0] = NULL;
11532 currentMove = forwardMostMove = backwardMostMove = 0;
11534 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11536 initialRulePlies = FENrulePlies;
11537 for( i=0; i< nrCastlingRights; i++ )
11538 initialRights[i] = initial_position[CASTLING][i];
11540 yyboardindex = forwardMostMove;
11541 free(gameInfo.fen);
11542 gameInfo.fen = NULL;
11545 yyboardindex = forwardMostMove;
11546 cm = (ChessMove) Myylex();
11548 /* Handle comments interspersed among the tags */
11549 while (cm == Comment) {
11551 if (appData.debugMode)
11552 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11554 AppendComment(currentMove, p, FALSE);
11555 yyboardindex = forwardMostMove;
11556 cm = (ChessMove) Myylex();
11560 /* don't rely on existence of Event tag since if game was
11561 * pasted from clipboard the Event tag may not exist
11563 if (numPGNTags > 0){
11565 if (gameInfo.variant == VariantNormal) {
11566 VariantClass v = StringToVariant(gameInfo.event);
11567 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11568 if(v < VariantShogi) gameInfo.variant = v;
11571 if( appData.autoDisplayTags ) {
11572 tags = PGNTags(&gameInfo);
11573 TagsPopUp(tags, CmailMsg());
11578 /* Make something up, but don't display it now */
11583 if (cm == PositionDiagram) {
11586 Board initial_position;
11588 if (appData.debugMode)
11589 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11591 if (!startedFromSetupPosition) {
11593 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11594 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11605 initial_position[i][j++] = CharToPiece(*p);
11608 while (*p == ' ' || *p == '\t' ||
11609 *p == '\n' || *p == '\r') p++;
11611 if (strncmp(p, "black", strlen("black"))==0)
11612 blackPlaysFirst = TRUE;
11614 blackPlaysFirst = FALSE;
11615 startedFromSetupPosition = TRUE;
11617 CopyBoard(boards[0], initial_position);
11618 if (blackPlaysFirst) {
11619 currentMove = forwardMostMove = backwardMostMove = 1;
11620 CopyBoard(boards[1], initial_position);
11621 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11622 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11623 timeRemaining[0][1] = whiteTimeRemaining;
11624 timeRemaining[1][1] = blackTimeRemaining;
11625 if (commentList[0] != NULL) {
11626 commentList[1] = commentList[0];
11627 commentList[0] = NULL;
11630 currentMove = forwardMostMove = backwardMostMove = 0;
11633 yyboardindex = forwardMostMove;
11634 cm = (ChessMove) Myylex();
11637 if (first.pr == NoProc) {
11638 StartChessProgram(&first);
11640 InitChessProgram(&first, FALSE);
11641 SendToProgram("force\n", &first);
11642 if (startedFromSetupPosition) {
11643 SendBoard(&first, forwardMostMove);
11644 if (appData.debugMode) {
11645 fprintf(debugFP, "Load Game\n");
11647 DisplayBothClocks();
11650 /* [HGM] server: flag to write setup moves in broadcast file as one */
11651 loadFlag = appData.suppressLoadMoves;
11653 while (cm == Comment) {
11655 if (appData.debugMode)
11656 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11658 AppendComment(currentMove, p, FALSE);
11659 yyboardindex = forwardMostMove;
11660 cm = (ChessMove) Myylex();
11663 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11664 cm == WhiteWins || cm == BlackWins ||
11665 cm == GameIsDrawn || cm == GameUnfinished) {
11666 DisplayMessage("", _("No moves in game"));
11667 if (cmailMsgLoaded) {
11668 if (appData.debugMode)
11669 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11673 DrawPosition(FALSE, boards[currentMove]);
11674 DisplayBothClocks();
11675 gameMode = EditGame;
11682 // [HGM] PV info: routine tests if comment empty
11683 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11684 DisplayComment(currentMove - 1, commentList[currentMove]);
11686 if (!matchMode && appData.timeDelay != 0)
11687 DrawPosition(FALSE, boards[currentMove]);
11689 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11690 programStats.ok_to_send = 1;
11693 /* if the first token after the PGN tags is a move
11694 * and not move number 1, retrieve it from the parser
11696 if (cm != MoveNumberOne)
11697 LoadGameOneMove(cm);
11699 /* load the remaining moves from the file */
11700 while (LoadGameOneMove(EndOfFile)) {
11701 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11702 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11705 /* rewind to the start of the game */
11706 currentMove = backwardMostMove;
11708 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11710 if (oldGameMode == AnalyzeFile ||
11711 oldGameMode == AnalyzeMode) {
11712 AnalyzeFileEvent();
11715 if (!matchMode && pos >= 0) {
11716 ToNrEvent(pos); // [HGM] no autoplay if selected on position
11718 if (matchMode || appData.timeDelay == 0) {
11720 } else if (appData.timeDelay > 0) {
11721 AutoPlayGameLoop();
11724 if (appData.debugMode)
11725 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11727 loadFlag = 0; /* [HGM] true game starts */
11731 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11733 ReloadPosition(offset)
11736 int positionNumber = lastLoadPositionNumber + offset;
11737 if (lastLoadPositionFP == NULL) {
11738 DisplayError(_("No position has been loaded yet"), 0);
11741 if (positionNumber <= 0) {
11742 DisplayError(_("Can't back up any further"), 0);
11745 return LoadPosition(lastLoadPositionFP, positionNumber,
11746 lastLoadPositionTitle);
11749 /* Load the nth position from the given file */
11751 LoadPositionFromFile(filename, n, title)
11759 if (strcmp(filename, "-") == 0) {
11760 return LoadPosition(stdin, n, "stdin");
11762 f = fopen(filename, "rb");
11764 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11765 DisplayError(buf, errno);
11768 return LoadPosition(f, n, title);
11773 /* Load the nth position from the given open file, and close it */
11775 LoadPosition(f, positionNumber, title)
11777 int positionNumber;
11780 char *p, line[MSG_SIZ];
11781 Board initial_position;
11782 int i, j, fenMode, pn;
11784 if (gameMode == Training )
11785 SetTrainingModeOff();
11787 if (gameMode != BeginningOfGame) {
11788 Reset(FALSE, TRUE);
11790 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11791 fclose(lastLoadPositionFP);
11793 if (positionNumber == 0) positionNumber = 1;
11794 lastLoadPositionFP = f;
11795 lastLoadPositionNumber = positionNumber;
11796 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11797 if (first.pr == NoProc) {
11798 StartChessProgram(&first);
11799 InitChessProgram(&first, FALSE);
11801 pn = positionNumber;
11802 if (positionNumber < 0) {
11803 /* Negative position number means to seek to that byte offset */
11804 if (fseek(f, -positionNumber, 0) == -1) {
11805 DisplayError(_("Can't seek on position file"), 0);
11810 if (fseek(f, 0, 0) == -1) {
11811 if (f == lastLoadPositionFP ?
11812 positionNumber == lastLoadPositionNumber + 1 :
11813 positionNumber == 1) {
11816 DisplayError(_("Can't seek on position file"), 0);
11821 /* See if this file is FEN or old-style xboard */
11822 if (fgets(line, MSG_SIZ, f) == NULL) {
11823 DisplayError(_("Position not found in file"), 0);
11826 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11827 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11830 if (fenMode || line[0] == '#') pn--;
11832 /* skip positions before number pn */
11833 if (fgets(line, MSG_SIZ, f) == NULL) {
11835 DisplayError(_("Position not found in file"), 0);
11838 if (fenMode || line[0] == '#') pn--;
11843 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11844 DisplayError(_("Bad FEN position in file"), 0);
11848 (void) fgets(line, MSG_SIZ, f);
11849 (void) fgets(line, MSG_SIZ, f);
11851 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11852 (void) fgets(line, MSG_SIZ, f);
11853 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11856 initial_position[i][j++] = CharToPiece(*p);
11860 blackPlaysFirst = FALSE;
11862 (void) fgets(line, MSG_SIZ, f);
11863 if (strncmp(line, "black", strlen("black"))==0)
11864 blackPlaysFirst = TRUE;
11867 startedFromSetupPosition = TRUE;
11869 SendToProgram("force\n", &first);
11870 CopyBoard(boards[0], initial_position);
11871 if (blackPlaysFirst) {
11872 currentMove = forwardMostMove = backwardMostMove = 1;
11873 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11874 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11875 CopyBoard(boards[1], initial_position);
11876 DisplayMessage("", _("Black to play"));
11878 currentMove = forwardMostMove = backwardMostMove = 0;
11879 DisplayMessage("", _("White to play"));
11881 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11882 SendBoard(&first, forwardMostMove);
11883 if (appData.debugMode) {
11885 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11886 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11887 fprintf(debugFP, "Load Position\n");
11890 if (positionNumber > 1) {
11891 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11892 DisplayTitle(line);
11894 DisplayTitle(title);
11896 gameMode = EditGame;
11899 timeRemaining[0][1] = whiteTimeRemaining;
11900 timeRemaining[1][1] = blackTimeRemaining;
11901 DrawPosition(FALSE, boards[currentMove]);
11908 CopyPlayerNameIntoFileName(dest, src)
11911 while (*src != NULLCHAR && *src != ',') {
11916 *(*dest)++ = *src++;
11921 char *DefaultFileName(ext)
11924 static char def[MSG_SIZ];
11927 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11929 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11931 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11933 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11940 /* Save the current game to the given file */
11942 SaveGameToFile(filename, append)
11950 if (strcmp(filename, "-") == 0) {
11951 return SaveGame(stdout, 0, NULL);
11953 f = fopen(filename, append ? "a" : "w");
11955 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11956 DisplayError(buf, errno);
11959 safeStrCpy(buf, lastMsg, MSG_SIZ);
11960 DisplayMessage(_("Waiting for access to save file"), "");
11961 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11962 DisplayMessage(_("Saving game"), "");
11963 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11964 result = SaveGame(f, 0, NULL);
11965 DisplayMessage(buf, "");
11975 static char buf[MSG_SIZ];
11978 p = strchr(str, ' ');
11979 if (p == NULL) return str;
11980 strncpy(buf, str, p - str);
11981 buf[p - str] = NULLCHAR;
11985 #define PGN_MAX_LINE 75
11987 #define PGN_SIDE_WHITE 0
11988 #define PGN_SIDE_BLACK 1
11991 static int FindFirstMoveOutOfBook( int side )
11995 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11996 int index = backwardMostMove;
11997 int has_book_hit = 0;
11999 if( (index % 2) != side ) {
12003 while( index < forwardMostMove ) {
12004 /* Check to see if engine is in book */
12005 int depth = pvInfoList[index].depth;
12006 int score = pvInfoList[index].score;
12012 else if( score == 0 && depth == 63 ) {
12013 in_book = 1; /* Zappa */
12015 else if( score == 2 && depth == 99 ) {
12016 in_book = 1; /* Abrok */
12019 has_book_hit += in_book;
12035 void GetOutOfBookInfo( char * buf )
12039 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12041 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12042 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12046 if( oob[0] >= 0 || oob[1] >= 0 ) {
12047 for( i=0; i<2; i++ ) {
12051 if( i > 0 && oob[0] >= 0 ) {
12052 strcat( buf, " " );
12055 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12056 sprintf( buf+strlen(buf), "%s%.2f",
12057 pvInfoList[idx].score >= 0 ? "+" : "",
12058 pvInfoList[idx].score / 100.0 );
12064 /* Save game in PGN style and close the file */
12069 int i, offset, linelen, newblock;
12073 int movelen, numlen, blank;
12074 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12076 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12078 tm = time((time_t *) NULL);
12080 PrintPGNTags(f, &gameInfo);
12082 if (backwardMostMove > 0 || startedFromSetupPosition) {
12083 char *fen = PositionToFEN(backwardMostMove, NULL);
12084 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12085 fprintf(f, "\n{--------------\n");
12086 PrintPosition(f, backwardMostMove);
12087 fprintf(f, "--------------}\n");
12091 /* [AS] Out of book annotation */
12092 if( appData.saveOutOfBookInfo ) {
12095 GetOutOfBookInfo( buf );
12097 if( buf[0] != '\0' ) {
12098 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12105 i = backwardMostMove;
12109 while (i < forwardMostMove) {
12110 /* Print comments preceding this move */
12111 if (commentList[i] != NULL) {
12112 if (linelen > 0) fprintf(f, "\n");
12113 fprintf(f, "%s", commentList[i]);
12118 /* Format move number */
12120 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12123 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12125 numtext[0] = NULLCHAR;
12127 numlen = strlen(numtext);
12130 /* Print move number */
12131 blank = linelen > 0 && numlen > 0;
12132 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12141 fprintf(f, "%s", numtext);
12145 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12146 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12149 blank = linelen > 0 && movelen > 0;
12150 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12159 fprintf(f, "%s", move_buffer);
12160 linelen += movelen;
12162 /* [AS] Add PV info if present */
12163 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12164 /* [HGM] add time */
12165 char buf[MSG_SIZ]; int seconds;
12167 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12173 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12176 seconds = (seconds + 4)/10; // round to full seconds
12178 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12180 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12183 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12184 pvInfoList[i].score >= 0 ? "+" : "",
12185 pvInfoList[i].score / 100.0,
12186 pvInfoList[i].depth,
12189 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12191 /* Print score/depth */
12192 blank = linelen > 0 && movelen > 0;
12193 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12202 fprintf(f, "%s", move_buffer);
12203 linelen += movelen;
12209 /* Start a new line */
12210 if (linelen > 0) fprintf(f, "\n");
12212 /* Print comments after last move */
12213 if (commentList[i] != NULL) {
12214 fprintf(f, "%s\n", commentList[i]);
12218 if (gameInfo.resultDetails != NULL &&
12219 gameInfo.resultDetails[0] != NULLCHAR) {
12220 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12221 PGNResult(gameInfo.result));
12223 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12227 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12231 /* Save game in old style and close the file */
12233 SaveGameOldStyle(f)
12239 tm = time((time_t *) NULL);
12241 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12244 if (backwardMostMove > 0 || startedFromSetupPosition) {
12245 fprintf(f, "\n[--------------\n");
12246 PrintPosition(f, backwardMostMove);
12247 fprintf(f, "--------------]\n");
12252 i = backwardMostMove;
12253 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12255 while (i < forwardMostMove) {
12256 if (commentList[i] != NULL) {
12257 fprintf(f, "[%s]\n", commentList[i]);
12260 if ((i % 2) == 1) {
12261 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12264 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12266 if (commentList[i] != NULL) {
12270 if (i >= forwardMostMove) {
12274 fprintf(f, "%s\n", parseList[i]);
12279 if (commentList[i] != NULL) {
12280 fprintf(f, "[%s]\n", commentList[i]);
12283 /* This isn't really the old style, but it's close enough */
12284 if (gameInfo.resultDetails != NULL &&
12285 gameInfo.resultDetails[0] != NULLCHAR) {
12286 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12287 gameInfo.resultDetails);
12289 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12296 /* Save the current game to open file f and close the file */
12298 SaveGame(f, dummy, dummy2)
12303 if (gameMode == EditPosition) EditPositionDone(TRUE);
12304 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12305 if (appData.oldSaveStyle)
12306 return SaveGameOldStyle(f);
12308 return SaveGamePGN(f);
12311 /* Save the current position to the given file */
12313 SavePositionToFile(filename)
12319 if (strcmp(filename, "-") == 0) {
12320 return SavePosition(stdout, 0, NULL);
12322 f = fopen(filename, "a");
12324 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12325 DisplayError(buf, errno);
12328 safeStrCpy(buf, lastMsg, MSG_SIZ);
12329 DisplayMessage(_("Waiting for access to save file"), "");
12330 flock(fileno(f), LOCK_EX); // [HGM] lock
12331 DisplayMessage(_("Saving position"), "");
12332 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12333 SavePosition(f, 0, NULL);
12334 DisplayMessage(buf, "");
12340 /* Save the current position to the given open file and close the file */
12342 SavePosition(f, dummy, dummy2)
12350 if (gameMode == EditPosition) EditPositionDone(TRUE);
12351 if (appData.oldSaveStyle) {
12352 tm = time((time_t *) NULL);
12354 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12356 fprintf(f, "[--------------\n");
12357 PrintPosition(f, currentMove);
12358 fprintf(f, "--------------]\n");
12360 fen = PositionToFEN(currentMove, NULL);
12361 fprintf(f, "%s\n", fen);
12369 ReloadCmailMsgEvent(unregister)
12373 static char *inFilename = NULL;
12374 static char *outFilename;
12376 struct stat inbuf, outbuf;
12379 /* Any registered moves are unregistered if unregister is set, */
12380 /* i.e. invoked by the signal handler */
12382 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12383 cmailMoveRegistered[i] = FALSE;
12384 if (cmailCommentList[i] != NULL) {
12385 free(cmailCommentList[i]);
12386 cmailCommentList[i] = NULL;
12389 nCmailMovesRegistered = 0;
12392 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12393 cmailResult[i] = CMAIL_NOT_RESULT;
12397 if (inFilename == NULL) {
12398 /* Because the filenames are static they only get malloced once */
12399 /* and they never get freed */
12400 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12401 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12403 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12404 sprintf(outFilename, "%s.out", appData.cmailGameName);
12407 status = stat(outFilename, &outbuf);
12409 cmailMailedMove = FALSE;
12411 status = stat(inFilename, &inbuf);
12412 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12415 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12416 counts the games, notes how each one terminated, etc.
12418 It would be nice to remove this kludge and instead gather all
12419 the information while building the game list. (And to keep it
12420 in the game list nodes instead of having a bunch of fixed-size
12421 parallel arrays.) Note this will require getting each game's
12422 termination from the PGN tags, as the game list builder does
12423 not process the game moves. --mann
12425 cmailMsgLoaded = TRUE;
12426 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12428 /* Load first game in the file or popup game menu */
12429 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12431 #endif /* !WIN32 */
12439 char string[MSG_SIZ];
12441 if ( cmailMailedMove
12442 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12443 return TRUE; /* Allow free viewing */
12446 /* Unregister move to ensure that we don't leave RegisterMove */
12447 /* with the move registered when the conditions for registering no */
12449 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12450 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12451 nCmailMovesRegistered --;
12453 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12455 free(cmailCommentList[lastLoadGameNumber - 1]);
12456 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12460 if (cmailOldMove == -1) {
12461 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12465 if (currentMove > cmailOldMove + 1) {
12466 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12470 if (currentMove < cmailOldMove) {
12471 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12475 if (forwardMostMove > currentMove) {
12476 /* Silently truncate extra moves */
12480 if ( (currentMove == cmailOldMove + 1)
12481 || ( (currentMove == cmailOldMove)
12482 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12483 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12484 if (gameInfo.result != GameUnfinished) {
12485 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12488 if (commentList[currentMove] != NULL) {
12489 cmailCommentList[lastLoadGameNumber - 1]
12490 = StrSave(commentList[currentMove]);
12492 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12494 if (appData.debugMode)
12495 fprintf(debugFP, "Saving %s for game %d\n",
12496 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12498 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12500 f = fopen(string, "w");
12501 if (appData.oldSaveStyle) {
12502 SaveGameOldStyle(f); /* also closes the file */
12504 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12505 f = fopen(string, "w");
12506 SavePosition(f, 0, NULL); /* also closes the file */
12508 fprintf(f, "{--------------\n");
12509 PrintPosition(f, currentMove);
12510 fprintf(f, "--------------}\n\n");
12512 SaveGame(f, 0, NULL); /* also closes the file*/
12515 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12516 nCmailMovesRegistered ++;
12517 } else if (nCmailGames == 1) {
12518 DisplayError(_("You have not made a move yet"), 0);
12529 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12530 FILE *commandOutput;
12531 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12532 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12538 if (! cmailMsgLoaded) {
12539 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12543 if (nCmailGames == nCmailResults) {
12544 DisplayError(_("No unfinished games"), 0);
12548 #if CMAIL_PROHIBIT_REMAIL
12549 if (cmailMailedMove) {
12550 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);
12551 DisplayError(msg, 0);
12556 if (! (cmailMailedMove || RegisterMove())) return;
12558 if ( cmailMailedMove
12559 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12560 snprintf(string, MSG_SIZ, partCommandString,
12561 appData.debugMode ? " -v" : "", appData.cmailGameName);
12562 commandOutput = popen(string, "r");
12564 if (commandOutput == NULL) {
12565 DisplayError(_("Failed to invoke cmail"), 0);
12567 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12568 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12570 if (nBuffers > 1) {
12571 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12572 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12573 nBytes = MSG_SIZ - 1;
12575 (void) memcpy(msg, buffer, nBytes);
12577 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12579 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12580 cmailMailedMove = TRUE; /* Prevent >1 moves */
12583 for (i = 0; i < nCmailGames; i ++) {
12584 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12589 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12591 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12593 appData.cmailGameName,
12595 LoadGameFromFile(buffer, 1, buffer, FALSE);
12596 cmailMsgLoaded = FALSE;
12600 DisplayInformation(msg);
12601 pclose(commandOutput);
12604 if ((*cmailMsg) != '\0') {
12605 DisplayInformation(cmailMsg);
12610 #endif /* !WIN32 */
12619 int prependComma = 0;
12621 char string[MSG_SIZ]; /* Space for game-list */
12624 if (!cmailMsgLoaded) return "";
12626 if (cmailMailedMove) {
12627 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12629 /* Create a list of games left */
12630 snprintf(string, MSG_SIZ, "[");
12631 for (i = 0; i < nCmailGames; i ++) {
12632 if (! ( cmailMoveRegistered[i]
12633 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12634 if (prependComma) {
12635 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12637 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12641 strcat(string, number);
12644 strcat(string, "]");
12646 if (nCmailMovesRegistered + nCmailResults == 0) {
12647 switch (nCmailGames) {
12649 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12653 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12657 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12662 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12664 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12669 if (nCmailResults == nCmailGames) {
12670 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12672 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12677 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12689 if (gameMode == Training)
12690 SetTrainingModeOff();
12693 cmailMsgLoaded = FALSE;
12694 if (appData.icsActive) {
12695 SendToICS(ics_prefix);
12696 SendToICS("refresh\n");
12706 /* Give up on clean exit */
12710 /* Keep trying for clean exit */
12714 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12716 if (telnetISR != NULL) {
12717 RemoveInputSource(telnetISR);
12719 if (icsPR != NoProc) {
12720 DestroyChildProcess(icsPR, TRUE);
12723 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12724 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12726 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12727 /* make sure this other one finishes before killing it! */
12728 if(endingGame) { int count = 0;
12729 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12730 while(endingGame && count++ < 10) DoSleep(1);
12731 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12734 /* Kill off chess programs */
12735 if (first.pr != NoProc) {
12738 DoSleep( appData.delayBeforeQuit );
12739 SendToProgram("quit\n", &first);
12740 DoSleep( appData.delayAfterQuit );
12741 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12743 if (second.pr != NoProc) {
12744 DoSleep( appData.delayBeforeQuit );
12745 SendToProgram("quit\n", &second);
12746 DoSleep( appData.delayAfterQuit );
12747 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12749 if (first.isr != NULL) {
12750 RemoveInputSource(first.isr);
12752 if (second.isr != NULL) {
12753 RemoveInputSource(second.isr);
12756 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12757 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12759 ShutDownFrontEnd();
12766 if (appData.debugMode)
12767 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12771 if (gameMode == MachinePlaysWhite ||
12772 gameMode == MachinePlaysBlack) {
12775 DisplayBothClocks();
12777 if (gameMode == PlayFromGameFile) {
12778 if (appData.timeDelay >= 0)
12779 AutoPlayGameLoop();
12780 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12781 Reset(FALSE, TRUE);
12782 SendToICS(ics_prefix);
12783 SendToICS("refresh\n");
12784 } else if (currentMove < forwardMostMove) {
12785 ForwardInner(forwardMostMove);
12787 pauseExamInvalid = FALSE;
12789 switch (gameMode) {
12793 pauseExamForwardMostMove = forwardMostMove;
12794 pauseExamInvalid = FALSE;
12797 case IcsPlayingWhite:
12798 case IcsPlayingBlack:
12802 case PlayFromGameFile:
12803 (void) StopLoadGameTimer();
12807 case BeginningOfGame:
12808 if (appData.icsActive) return;
12809 /* else fall through */
12810 case MachinePlaysWhite:
12811 case MachinePlaysBlack:
12812 case TwoMachinesPlay:
12813 if (forwardMostMove == 0)
12814 return; /* don't pause if no one has moved */
12815 if ((gameMode == MachinePlaysWhite &&
12816 !WhiteOnMove(forwardMostMove)) ||
12817 (gameMode == MachinePlaysBlack &&
12818 WhiteOnMove(forwardMostMove))) {
12831 char title[MSG_SIZ];
12833 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12834 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12836 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12837 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12838 parseList[currentMove - 1]);
12841 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12848 char *tags = PGNTags(&gameInfo);
12850 EditTagsPopUp(tags, NULL);
12857 if (appData.noChessProgram || gameMode == AnalyzeMode)
12860 if (gameMode != AnalyzeFile) {
12861 if (!appData.icsEngineAnalyze) {
12863 if (gameMode != EditGame) return;
12865 ResurrectChessProgram();
12866 SendToProgram("analyze\n", &first);
12867 first.analyzing = TRUE;
12868 /*first.maybeThinking = TRUE;*/
12869 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12870 EngineOutputPopUp();
12872 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12877 StartAnalysisClock();
12878 GetTimeMark(&lastNodeCountTime);
12885 if (appData.noChessProgram || gameMode == AnalyzeFile)
12888 if (gameMode != AnalyzeMode) {
12890 if (gameMode != EditGame) return;
12891 ResurrectChessProgram();
12892 SendToProgram("analyze\n", &first);
12893 first.analyzing = TRUE;
12894 /*first.maybeThinking = TRUE;*/
12895 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12896 EngineOutputPopUp();
12898 gameMode = AnalyzeFile;
12903 StartAnalysisClock();
12904 GetTimeMark(&lastNodeCountTime);
12906 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12910 MachineWhiteEvent()
12913 char *bookHit = NULL;
12915 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12919 if (gameMode == PlayFromGameFile ||
12920 gameMode == TwoMachinesPlay ||
12921 gameMode == Training ||
12922 gameMode == AnalyzeMode ||
12923 gameMode == EndOfGame)
12926 if (gameMode == EditPosition)
12927 EditPositionDone(TRUE);
12929 if (!WhiteOnMove(currentMove)) {
12930 DisplayError(_("It is not White's turn"), 0);
12934 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12937 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12938 gameMode == AnalyzeFile)
12941 ResurrectChessProgram(); /* in case it isn't running */
12942 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12943 gameMode = MachinePlaysWhite;
12946 gameMode = MachinePlaysWhite;
12950 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12952 if (first.sendName) {
12953 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12954 SendToProgram(buf, &first);
12956 if (first.sendTime) {
12957 if (first.useColors) {
12958 SendToProgram("black\n", &first); /*gnu kludge*/
12960 SendTimeRemaining(&first, TRUE);
12962 if (first.useColors) {
12963 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12965 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12966 SetMachineThinkingEnables();
12967 first.maybeThinking = TRUE;
12971 if (appData.autoFlipView && !flipView) {
12972 flipView = !flipView;
12973 DrawPosition(FALSE, NULL);
12974 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12977 if(bookHit) { // [HGM] book: simulate book reply
12978 static char bookMove[MSG_SIZ]; // a bit generous?
12980 programStats.nodes = programStats.depth = programStats.time =
12981 programStats.score = programStats.got_only_move = 0;
12982 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12984 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12985 strcat(bookMove, bookHit);
12986 HandleMachineMove(bookMove, &first);
12991 MachineBlackEvent()
12994 char *bookHit = NULL;
12996 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13000 if (gameMode == PlayFromGameFile ||
13001 gameMode == TwoMachinesPlay ||
13002 gameMode == Training ||
13003 gameMode == AnalyzeMode ||
13004 gameMode == EndOfGame)
13007 if (gameMode == EditPosition)
13008 EditPositionDone(TRUE);
13010 if (WhiteOnMove(currentMove)) {
13011 DisplayError(_("It is not Black's turn"), 0);
13015 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13018 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13019 gameMode == AnalyzeFile)
13022 ResurrectChessProgram(); /* in case it isn't running */
13023 gameMode = MachinePlaysBlack;
13027 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13029 if (first.sendName) {
13030 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13031 SendToProgram(buf, &first);
13033 if (first.sendTime) {
13034 if (first.useColors) {
13035 SendToProgram("white\n", &first); /*gnu kludge*/
13037 SendTimeRemaining(&first, FALSE);
13039 if (first.useColors) {
13040 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13042 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13043 SetMachineThinkingEnables();
13044 first.maybeThinking = TRUE;
13047 if (appData.autoFlipView && flipView) {
13048 flipView = !flipView;
13049 DrawPosition(FALSE, NULL);
13050 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13052 if(bookHit) { // [HGM] book: simulate book reply
13053 static char bookMove[MSG_SIZ]; // a bit generous?
13055 programStats.nodes = programStats.depth = programStats.time =
13056 programStats.score = programStats.got_only_move = 0;
13057 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13059 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13060 strcat(bookMove, bookHit);
13061 HandleMachineMove(bookMove, &first);
13067 DisplayTwoMachinesTitle()
13070 if (appData.matchGames > 0) {
13071 if(appData.tourneyFile[0]) {
13072 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13073 gameInfo.white, gameInfo.black,
13074 nextGame+1, appData.matchGames+1,
13075 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13077 if (first.twoMachinesColor[0] == 'w') {
13078 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13079 gameInfo.white, gameInfo.black,
13080 first.matchWins, second.matchWins,
13081 matchGame - 1 - (first.matchWins + second.matchWins));
13083 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13084 gameInfo.white, gameInfo.black,
13085 second.matchWins, first.matchWins,
13086 matchGame - 1 - (first.matchWins + second.matchWins));
13089 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13095 SettingsMenuIfReady()
13097 if (second.lastPing != second.lastPong) {
13098 DisplayMessage("", _("Waiting for second chess program"));
13099 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13103 DisplayMessage("", "");
13104 SettingsPopUp(&second);
13108 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13111 if (cps->pr == NULL) {
13112 StartChessProgram(cps);
13113 if (cps->protocolVersion == 1) {
13116 /* kludge: allow timeout for initial "feature" command */
13118 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13119 DisplayMessage("", buf);
13120 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13128 TwoMachinesEvent P((void))
13132 ChessProgramState *onmove;
13133 char *bookHit = NULL;
13134 static int stalling = 0;
13138 if (appData.noChessProgram) return;
13140 switch (gameMode) {
13141 case TwoMachinesPlay:
13143 case MachinePlaysWhite:
13144 case MachinePlaysBlack:
13145 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13146 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13150 case BeginningOfGame:
13151 case PlayFromGameFile:
13154 if (gameMode != EditGame) return;
13157 EditPositionDone(TRUE);
13168 // forwardMostMove = currentMove;
13169 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13171 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13173 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13174 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13175 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13179 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13180 SendToProgram("force\n", &second);
13182 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13185 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13186 if(appData.matchPause>10000 || appData.matchPause<10)
13187 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13188 wait = SubtractTimeMarks(&now, &pauseStart);
13189 if(wait < appData.matchPause) {
13190 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13194 DisplayMessage("", "");
13195 if (startedFromSetupPosition) {
13196 SendBoard(&second, backwardMostMove);
13197 if (appData.debugMode) {
13198 fprintf(debugFP, "Two Machines\n");
13201 for (i = backwardMostMove; i < forwardMostMove; i++) {
13202 SendMoveToProgram(i, &second);
13205 gameMode = TwoMachinesPlay;
13207 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13209 DisplayTwoMachinesTitle();
13211 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13216 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13217 SendToProgram(first.computerString, &first);
13218 if (first.sendName) {
13219 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13220 SendToProgram(buf, &first);
13222 SendToProgram(second.computerString, &second);
13223 if (second.sendName) {
13224 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13225 SendToProgram(buf, &second);
13229 if (!first.sendTime || !second.sendTime) {
13230 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13231 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13233 if (onmove->sendTime) {
13234 if (onmove->useColors) {
13235 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13237 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13239 if (onmove->useColors) {
13240 SendToProgram(onmove->twoMachinesColor, onmove);
13242 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13243 // SendToProgram("go\n", onmove);
13244 onmove->maybeThinking = TRUE;
13245 SetMachineThinkingEnables();
13249 if(bookHit) { // [HGM] book: simulate book reply
13250 static char bookMove[MSG_SIZ]; // a bit generous?
13252 programStats.nodes = programStats.depth = programStats.time =
13253 programStats.score = programStats.got_only_move = 0;
13254 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13256 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13257 strcat(bookMove, bookHit);
13258 savedMessage = bookMove; // args for deferred call
13259 savedState = onmove;
13260 ScheduleDelayedEvent(DeferredBookMove, 1);
13267 if (gameMode == Training) {
13268 SetTrainingModeOff();
13269 gameMode = PlayFromGameFile;
13270 DisplayMessage("", _("Training mode off"));
13272 gameMode = Training;
13273 animateTraining = appData.animate;
13275 /* make sure we are not already at the end of the game */
13276 if (currentMove < forwardMostMove) {
13277 SetTrainingModeOn();
13278 DisplayMessage("", _("Training mode on"));
13280 gameMode = PlayFromGameFile;
13281 DisplayError(_("Already at end of game"), 0);
13290 if (!appData.icsActive) return;
13291 switch (gameMode) {
13292 case IcsPlayingWhite:
13293 case IcsPlayingBlack:
13296 case BeginningOfGame:
13304 EditPositionDone(TRUE);
13317 gameMode = IcsIdle;
13328 switch (gameMode) {
13330 SetTrainingModeOff();
13332 case MachinePlaysWhite:
13333 case MachinePlaysBlack:
13334 case BeginningOfGame:
13335 SendToProgram("force\n", &first);
13336 SetUserThinkingEnables();
13338 case PlayFromGameFile:
13339 (void) StopLoadGameTimer();
13340 if (gameFileFP != NULL) {
13345 EditPositionDone(TRUE);
13350 SendToProgram("force\n", &first);
13352 case TwoMachinesPlay:
13353 GameEnds(EndOfFile, NULL, GE_PLAYER);
13354 ResurrectChessProgram();
13355 SetUserThinkingEnables();
13358 ResurrectChessProgram();
13360 case IcsPlayingBlack:
13361 case IcsPlayingWhite:
13362 DisplayError(_("Warning: You are still playing a game"), 0);
13365 DisplayError(_("Warning: You are still observing a game"), 0);
13368 DisplayError(_("Warning: You are still examining a game"), 0);
13379 first.offeredDraw = second.offeredDraw = 0;
13381 if (gameMode == PlayFromGameFile) {
13382 whiteTimeRemaining = timeRemaining[0][currentMove];
13383 blackTimeRemaining = timeRemaining[1][currentMove];
13387 if (gameMode == MachinePlaysWhite ||
13388 gameMode == MachinePlaysBlack ||
13389 gameMode == TwoMachinesPlay ||
13390 gameMode == EndOfGame) {
13391 i = forwardMostMove;
13392 while (i > currentMove) {
13393 SendToProgram("undo\n", &first);
13396 whiteTimeRemaining = timeRemaining[0][currentMove];
13397 blackTimeRemaining = timeRemaining[1][currentMove];
13398 DisplayBothClocks();
13399 if (whiteFlag || blackFlag) {
13400 whiteFlag = blackFlag = 0;
13405 gameMode = EditGame;
13412 EditPositionEvent()
13414 if (gameMode == EditPosition) {
13420 if (gameMode != EditGame) return;
13422 gameMode = EditPosition;
13425 if (currentMove > 0)
13426 CopyBoard(boards[0], boards[currentMove]);
13428 blackPlaysFirst = !WhiteOnMove(currentMove);
13430 currentMove = forwardMostMove = backwardMostMove = 0;
13431 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13438 /* [DM] icsEngineAnalyze - possible call from other functions */
13439 if (appData.icsEngineAnalyze) {
13440 appData.icsEngineAnalyze = FALSE;
13442 DisplayMessage("",_("Close ICS engine analyze..."));
13444 if (first.analysisSupport && first.analyzing) {
13445 SendToProgram("exit\n", &first);
13446 first.analyzing = FALSE;
13448 thinkOutput[0] = NULLCHAR;
13452 EditPositionDone(Boolean fakeRights)
13454 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13456 startedFromSetupPosition = TRUE;
13457 InitChessProgram(&first, FALSE);
13458 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13459 boards[0][EP_STATUS] = EP_NONE;
13460 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13461 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13462 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13463 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13464 } else boards[0][CASTLING][2] = NoRights;
13465 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13466 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13467 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13468 } else boards[0][CASTLING][5] = NoRights;
13470 SendToProgram("force\n", &first);
13471 if (blackPlaysFirst) {
13472 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13473 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13474 currentMove = forwardMostMove = backwardMostMove = 1;
13475 CopyBoard(boards[1], boards[0]);
13477 currentMove = forwardMostMove = backwardMostMove = 0;
13479 SendBoard(&first, forwardMostMove);
13480 if (appData.debugMode) {
13481 fprintf(debugFP, "EditPosDone\n");
13484 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13485 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13486 gameMode = EditGame;
13488 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13489 ClearHighlights(); /* [AS] */
13492 /* Pause for `ms' milliseconds */
13493 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13503 } while (SubtractTimeMarks(&m2, &m1) < ms);
13506 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13508 SendMultiLineToICS(buf)
13511 char temp[MSG_SIZ+1], *p;
13518 strncpy(temp, buf, len);
13523 if (*p == '\n' || *p == '\r')
13528 strcat(temp, "\n");
13530 SendToPlayer(temp, strlen(temp));
13534 SetWhiteToPlayEvent()
13536 if (gameMode == EditPosition) {
13537 blackPlaysFirst = FALSE;
13538 DisplayBothClocks(); /* works because currentMove is 0 */
13539 } else if (gameMode == IcsExamining) {
13540 SendToICS(ics_prefix);
13541 SendToICS("tomove white\n");
13546 SetBlackToPlayEvent()
13548 if (gameMode == EditPosition) {
13549 blackPlaysFirst = TRUE;
13550 currentMove = 1; /* kludge */
13551 DisplayBothClocks();
13553 } else if (gameMode == IcsExamining) {
13554 SendToICS(ics_prefix);
13555 SendToICS("tomove black\n");
13560 EditPositionMenuEvent(selection, x, y)
13561 ChessSquare selection;
13565 ChessSquare piece = boards[0][y][x];
13567 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13569 switch (selection) {
13571 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13572 SendToICS(ics_prefix);
13573 SendToICS("bsetup clear\n");
13574 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13575 SendToICS(ics_prefix);
13576 SendToICS("clearboard\n");
13578 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13579 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13580 for (y = 0; y < BOARD_HEIGHT; y++) {
13581 if (gameMode == IcsExamining) {
13582 if (boards[currentMove][y][x] != EmptySquare) {
13583 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13588 boards[0][y][x] = p;
13593 if (gameMode == EditPosition) {
13594 DrawPosition(FALSE, boards[0]);
13599 SetWhiteToPlayEvent();
13603 SetBlackToPlayEvent();
13607 if (gameMode == IcsExamining) {
13608 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13609 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13612 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13613 if(x == BOARD_LEFT-2) {
13614 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13615 boards[0][y][1] = 0;
13617 if(x == BOARD_RGHT+1) {
13618 if(y >= gameInfo.holdingsSize) break;
13619 boards[0][y][BOARD_WIDTH-2] = 0;
13622 boards[0][y][x] = EmptySquare;
13623 DrawPosition(FALSE, boards[0]);
13628 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13629 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13630 selection = (ChessSquare) (PROMOTED piece);
13631 } else if(piece == EmptySquare) selection = WhiteSilver;
13632 else selection = (ChessSquare)((int)piece - 1);
13636 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13637 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13638 selection = (ChessSquare) (DEMOTED piece);
13639 } else if(piece == EmptySquare) selection = BlackSilver;
13640 else selection = (ChessSquare)((int)piece + 1);
13645 if(gameInfo.variant == VariantShatranj ||
13646 gameInfo.variant == VariantXiangqi ||
13647 gameInfo.variant == VariantCourier ||
13648 gameInfo.variant == VariantMakruk )
13649 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13654 if(gameInfo.variant == VariantXiangqi)
13655 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13656 if(gameInfo.variant == VariantKnightmate)
13657 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13660 if (gameMode == IcsExamining) {
13661 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13662 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13663 PieceToChar(selection), AAA + x, ONE + y);
13666 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13668 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13669 n = PieceToNumber(selection - BlackPawn);
13670 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13671 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13672 boards[0][BOARD_HEIGHT-1-n][1]++;
13674 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13675 n = PieceToNumber(selection);
13676 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13677 boards[0][n][BOARD_WIDTH-1] = selection;
13678 boards[0][n][BOARD_WIDTH-2]++;
13681 boards[0][y][x] = selection;
13682 DrawPosition(TRUE, boards[0]);
13690 DropMenuEvent(selection, x, y)
13691 ChessSquare selection;
13694 ChessMove moveType;
13696 switch (gameMode) {
13697 case IcsPlayingWhite:
13698 case MachinePlaysBlack:
13699 if (!WhiteOnMove(currentMove)) {
13700 DisplayMoveError(_("It is Black's turn"));
13703 moveType = WhiteDrop;
13705 case IcsPlayingBlack:
13706 case MachinePlaysWhite:
13707 if (WhiteOnMove(currentMove)) {
13708 DisplayMoveError(_("It is White's turn"));
13711 moveType = BlackDrop;
13714 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13720 if (moveType == BlackDrop && selection < BlackPawn) {
13721 selection = (ChessSquare) ((int) selection
13722 + (int) BlackPawn - (int) WhitePawn);
13724 if (boards[currentMove][y][x] != EmptySquare) {
13725 DisplayMoveError(_("That square is occupied"));
13729 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13735 /* Accept a pending offer of any kind from opponent */
13737 if (appData.icsActive) {
13738 SendToICS(ics_prefix);
13739 SendToICS("accept\n");
13740 } else if (cmailMsgLoaded) {
13741 if (currentMove == cmailOldMove &&
13742 commentList[cmailOldMove] != NULL &&
13743 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13744 "Black offers a draw" : "White offers a draw")) {
13746 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13747 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13749 DisplayError(_("There is no pending offer on this move"), 0);
13750 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13753 /* Not used for offers from chess program */
13760 /* Decline a pending offer of any kind from opponent */
13762 if (appData.icsActive) {
13763 SendToICS(ics_prefix);
13764 SendToICS("decline\n");
13765 } else if (cmailMsgLoaded) {
13766 if (currentMove == cmailOldMove &&
13767 commentList[cmailOldMove] != NULL &&
13768 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13769 "Black offers a draw" : "White offers a draw")) {
13771 AppendComment(cmailOldMove, "Draw declined", TRUE);
13772 DisplayComment(cmailOldMove - 1, "Draw declined");
13775 DisplayError(_("There is no pending offer on this move"), 0);
13778 /* Not used for offers from chess program */
13785 /* Issue ICS rematch command */
13786 if (appData.icsActive) {
13787 SendToICS(ics_prefix);
13788 SendToICS("rematch\n");
13795 /* Call your opponent's flag (claim a win on time) */
13796 if (appData.icsActive) {
13797 SendToICS(ics_prefix);
13798 SendToICS("flag\n");
13800 switch (gameMode) {
13803 case MachinePlaysWhite:
13806 GameEnds(GameIsDrawn, "Both players ran out of time",
13809 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13811 DisplayError(_("Your opponent is not out of time"), 0);
13814 case MachinePlaysBlack:
13817 GameEnds(GameIsDrawn, "Both players ran out of time",
13820 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13822 DisplayError(_("Your opponent is not out of time"), 0);
13830 ClockClick(int which)
13831 { // [HGM] code moved to back-end from winboard.c
13832 if(which) { // black clock
13833 if (gameMode == EditPosition || gameMode == IcsExamining) {
13834 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13835 SetBlackToPlayEvent();
13836 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13837 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13838 } else if (shiftKey) {
13839 AdjustClock(which, -1);
13840 } else if (gameMode == IcsPlayingWhite ||
13841 gameMode == MachinePlaysBlack) {
13844 } else { // white clock
13845 if (gameMode == EditPosition || gameMode == IcsExamining) {
13846 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13847 SetWhiteToPlayEvent();
13848 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13849 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13850 } else if (shiftKey) {
13851 AdjustClock(which, -1);
13852 } else if (gameMode == IcsPlayingBlack ||
13853 gameMode == MachinePlaysWhite) {
13862 /* Offer draw or accept pending draw offer from opponent */
13864 if (appData.icsActive) {
13865 /* Note: tournament rules require draw offers to be
13866 made after you make your move but before you punch
13867 your clock. Currently ICS doesn't let you do that;
13868 instead, you immediately punch your clock after making
13869 a move, but you can offer a draw at any time. */
13871 SendToICS(ics_prefix);
13872 SendToICS("draw\n");
13873 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13874 } else if (cmailMsgLoaded) {
13875 if (currentMove == cmailOldMove &&
13876 commentList[cmailOldMove] != NULL &&
13877 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13878 "Black offers a draw" : "White offers a draw")) {
13879 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13880 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13881 } else if (currentMove == cmailOldMove + 1) {
13882 char *offer = WhiteOnMove(cmailOldMove) ?
13883 "White offers a draw" : "Black offers a draw";
13884 AppendComment(currentMove, offer, TRUE);
13885 DisplayComment(currentMove - 1, offer);
13886 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13888 DisplayError(_("You must make your move before offering a draw"), 0);
13889 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13891 } else if (first.offeredDraw) {
13892 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13894 if (first.sendDrawOffers) {
13895 SendToProgram("draw\n", &first);
13896 userOfferedDraw = TRUE;
13904 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13906 if (appData.icsActive) {
13907 SendToICS(ics_prefix);
13908 SendToICS("adjourn\n");
13910 /* Currently GNU Chess doesn't offer or accept Adjourns */
13918 /* Offer Abort or accept pending Abort offer from opponent */
13920 if (appData.icsActive) {
13921 SendToICS(ics_prefix);
13922 SendToICS("abort\n");
13924 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13931 /* Resign. You can do this even if it's not your turn. */
13933 if (appData.icsActive) {
13934 SendToICS(ics_prefix);
13935 SendToICS("resign\n");
13937 switch (gameMode) {
13938 case MachinePlaysWhite:
13939 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13941 case MachinePlaysBlack:
13942 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13945 if (cmailMsgLoaded) {
13947 if (WhiteOnMove(cmailOldMove)) {
13948 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13950 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13952 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13963 StopObservingEvent()
13965 /* Stop observing current games */
13966 SendToICS(ics_prefix);
13967 SendToICS("unobserve\n");
13971 StopExaminingEvent()
13973 /* Stop observing current game */
13974 SendToICS(ics_prefix);
13975 SendToICS("unexamine\n");
13979 ForwardInner(target)
13984 if (appData.debugMode)
13985 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13986 target, currentMove, forwardMostMove);
13988 if (gameMode == EditPosition)
13991 if (gameMode == PlayFromGameFile && !pausing)
13994 if (gameMode == IcsExamining && pausing)
13995 limit = pauseExamForwardMostMove;
13997 limit = forwardMostMove;
13999 if (target > limit) target = limit;
14001 if (target > 0 && moveList[target - 1][0]) {
14002 int fromX, fromY, toX, toY;
14003 toX = moveList[target - 1][2] - AAA;
14004 toY = moveList[target - 1][3] - ONE;
14005 if (moveList[target - 1][1] == '@') {
14006 if (appData.highlightLastMove) {
14007 SetHighlights(-1, -1, toX, toY);
14010 fromX = moveList[target - 1][0] - AAA;
14011 fromY = moveList[target - 1][1] - ONE;
14012 if (target == currentMove + 1) {
14013 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14015 if (appData.highlightLastMove) {
14016 SetHighlights(fromX, fromY, toX, toY);
14020 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14021 gameMode == Training || gameMode == PlayFromGameFile ||
14022 gameMode == AnalyzeFile) {
14023 while (currentMove < target) {
14024 SendMoveToProgram(currentMove++, &first);
14027 currentMove = target;
14030 if (gameMode == EditGame || gameMode == EndOfGame) {
14031 whiteTimeRemaining = timeRemaining[0][currentMove];
14032 blackTimeRemaining = timeRemaining[1][currentMove];
14034 DisplayBothClocks();
14035 DisplayMove(currentMove - 1);
14036 DrawPosition(FALSE, boards[currentMove]);
14037 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14038 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14039 DisplayComment(currentMove - 1, commentList[currentMove]);
14041 DisplayBook(currentMove);
14048 if (gameMode == IcsExamining && !pausing) {
14049 SendToICS(ics_prefix);
14050 SendToICS("forward\n");
14052 ForwardInner(currentMove + 1);
14059 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14060 /* to optimze, we temporarily turn off analysis mode while we feed
14061 * the remaining moves to the engine. Otherwise we get analysis output
14064 if (first.analysisSupport) {
14065 SendToProgram("exit\nforce\n", &first);
14066 first.analyzing = FALSE;
14070 if (gameMode == IcsExamining && !pausing) {
14071 SendToICS(ics_prefix);
14072 SendToICS("forward 999999\n");
14074 ForwardInner(forwardMostMove);
14077 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14078 /* we have fed all the moves, so reactivate analysis mode */
14079 SendToProgram("analyze\n", &first);
14080 first.analyzing = TRUE;
14081 /*first.maybeThinking = TRUE;*/
14082 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14087 BackwardInner(target)
14090 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14092 if (appData.debugMode)
14093 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14094 target, currentMove, forwardMostMove);
14096 if (gameMode == EditPosition) return;
14097 if (currentMove <= backwardMostMove) {
14099 DrawPosition(full_redraw, boards[currentMove]);
14102 if (gameMode == PlayFromGameFile && !pausing)
14105 if (moveList[target][0]) {
14106 int fromX, fromY, toX, toY;
14107 toX = moveList[target][2] - AAA;
14108 toY = moveList[target][3] - ONE;
14109 if (moveList[target][1] == '@') {
14110 if (appData.highlightLastMove) {
14111 SetHighlights(-1, -1, toX, toY);
14114 fromX = moveList[target][0] - AAA;
14115 fromY = moveList[target][1] - ONE;
14116 if (target == currentMove - 1) {
14117 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14119 if (appData.highlightLastMove) {
14120 SetHighlights(fromX, fromY, toX, toY);
14124 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14125 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14126 while (currentMove > target) {
14127 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14128 // null move cannot be undone. Reload program with move history before it.
14130 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14131 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14133 SendBoard(&first, i);
14134 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14137 SendToProgram("undo\n", &first);
14141 currentMove = target;
14144 if (gameMode == EditGame || gameMode == EndOfGame) {
14145 whiteTimeRemaining = timeRemaining[0][currentMove];
14146 blackTimeRemaining = timeRemaining[1][currentMove];
14148 DisplayBothClocks();
14149 DisplayMove(currentMove - 1);
14150 DrawPosition(full_redraw, boards[currentMove]);
14151 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14152 // [HGM] PV info: routine tests if comment empty
14153 DisplayComment(currentMove - 1, commentList[currentMove]);
14154 DisplayBook(currentMove);
14160 if (gameMode == IcsExamining && !pausing) {
14161 SendToICS(ics_prefix);
14162 SendToICS("backward\n");
14164 BackwardInner(currentMove - 1);
14171 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14172 /* to optimize, we temporarily turn off analysis mode while we undo
14173 * all the moves. Otherwise we get analysis output after each undo.
14175 if (first.analysisSupport) {
14176 SendToProgram("exit\nforce\n", &first);
14177 first.analyzing = FALSE;
14181 if (gameMode == IcsExamining && !pausing) {
14182 SendToICS(ics_prefix);
14183 SendToICS("backward 999999\n");
14185 BackwardInner(backwardMostMove);
14188 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14189 /* we have fed all the moves, so reactivate analysis mode */
14190 SendToProgram("analyze\n", &first);
14191 first.analyzing = TRUE;
14192 /*first.maybeThinking = TRUE;*/
14193 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14200 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14201 if (to >= forwardMostMove) to = forwardMostMove;
14202 if (to <= backwardMostMove) to = backwardMostMove;
14203 if (to < currentMove) {
14211 RevertEvent(Boolean annotate)
14213 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14216 if (gameMode != IcsExamining) {
14217 DisplayError(_("You are not examining a game"), 0);
14221 DisplayError(_("You can't revert while pausing"), 0);
14224 SendToICS(ics_prefix);
14225 SendToICS("revert\n");
14231 switch (gameMode) {
14232 case MachinePlaysWhite:
14233 case MachinePlaysBlack:
14234 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14235 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14238 if (forwardMostMove < 2) return;
14239 currentMove = forwardMostMove = forwardMostMove - 2;
14240 whiteTimeRemaining = timeRemaining[0][currentMove];
14241 blackTimeRemaining = timeRemaining[1][currentMove];
14242 DisplayBothClocks();
14243 DisplayMove(currentMove - 1);
14244 ClearHighlights();/*!! could figure this out*/
14245 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14246 SendToProgram("remove\n", &first);
14247 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14250 case BeginningOfGame:
14254 case IcsPlayingWhite:
14255 case IcsPlayingBlack:
14256 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14257 SendToICS(ics_prefix);
14258 SendToICS("takeback 2\n");
14260 SendToICS(ics_prefix);
14261 SendToICS("takeback 1\n");
14270 ChessProgramState *cps;
14272 switch (gameMode) {
14273 case MachinePlaysWhite:
14274 if (!WhiteOnMove(forwardMostMove)) {
14275 DisplayError(_("It is your turn"), 0);
14280 case MachinePlaysBlack:
14281 if (WhiteOnMove(forwardMostMove)) {
14282 DisplayError(_("It is your turn"), 0);
14287 case TwoMachinesPlay:
14288 if (WhiteOnMove(forwardMostMove) ==
14289 (first.twoMachinesColor[0] == 'w')) {
14295 case BeginningOfGame:
14299 SendToProgram("?\n", cps);
14303 TruncateGameEvent()
14306 if (gameMode != EditGame) return;
14313 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14314 if (forwardMostMove > currentMove) {
14315 if (gameInfo.resultDetails != NULL) {
14316 free(gameInfo.resultDetails);
14317 gameInfo.resultDetails = NULL;
14318 gameInfo.result = GameUnfinished;
14320 forwardMostMove = currentMove;
14321 HistorySet(parseList, backwardMostMove, forwardMostMove,
14329 if (appData.noChessProgram) return;
14330 switch (gameMode) {
14331 case MachinePlaysWhite:
14332 if (WhiteOnMove(forwardMostMove)) {
14333 DisplayError(_("Wait until your turn"), 0);
14337 case BeginningOfGame:
14338 case MachinePlaysBlack:
14339 if (!WhiteOnMove(forwardMostMove)) {
14340 DisplayError(_("Wait until your turn"), 0);
14345 DisplayError(_("No hint available"), 0);
14348 SendToProgram("hint\n", &first);
14349 hintRequested = TRUE;
14355 if (appData.noChessProgram) return;
14356 switch (gameMode) {
14357 case MachinePlaysWhite:
14358 if (WhiteOnMove(forwardMostMove)) {
14359 DisplayError(_("Wait until your turn"), 0);
14363 case BeginningOfGame:
14364 case MachinePlaysBlack:
14365 if (!WhiteOnMove(forwardMostMove)) {
14366 DisplayError(_("Wait until your turn"), 0);
14371 EditPositionDone(TRUE);
14373 case TwoMachinesPlay:
14378 SendToProgram("bk\n", &first);
14379 bookOutput[0] = NULLCHAR;
14380 bookRequested = TRUE;
14386 char *tags = PGNTags(&gameInfo);
14387 TagsPopUp(tags, CmailMsg());
14391 /* end button procedures */
14394 PrintPosition(fp, move)
14400 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14401 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14402 char c = PieceToChar(boards[move][i][j]);
14403 fputc(c == 'x' ? '.' : c, fp);
14404 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14407 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14408 fprintf(fp, "white to play\n");
14410 fprintf(fp, "black to play\n");
14417 if (gameInfo.white != NULL) {
14418 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14424 /* Find last component of program's own name, using some heuristics */
14426 TidyProgramName(prog, host, buf)
14427 char *prog, *host, buf[MSG_SIZ];
14430 int local = (strcmp(host, "localhost") == 0);
14431 while (!local && (p = strchr(prog, ';')) != NULL) {
14433 while (*p == ' ') p++;
14436 if (*prog == '"' || *prog == '\'') {
14437 q = strchr(prog + 1, *prog);
14439 q = strchr(prog, ' ');
14441 if (q == NULL) q = prog + strlen(prog);
14443 while (p >= prog && *p != '/' && *p != '\\') p--;
14445 if(p == prog && *p == '"') p++;
14446 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14447 memcpy(buf, p, q - p);
14448 buf[q - p] = NULLCHAR;
14456 TimeControlTagValue()
14459 if (!appData.clockMode) {
14460 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14461 } else if (movesPerSession > 0) {
14462 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14463 } else if (timeIncrement == 0) {
14464 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14466 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14468 return StrSave(buf);
14474 /* This routine is used only for certain modes */
14475 VariantClass v = gameInfo.variant;
14476 ChessMove r = GameUnfinished;
14479 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14480 r = gameInfo.result;
14481 p = gameInfo.resultDetails;
14482 gameInfo.resultDetails = NULL;
14484 ClearGameInfo(&gameInfo);
14485 gameInfo.variant = v;
14487 switch (gameMode) {
14488 case MachinePlaysWhite:
14489 gameInfo.event = StrSave( appData.pgnEventHeader );
14490 gameInfo.site = StrSave(HostName());
14491 gameInfo.date = PGNDate();
14492 gameInfo.round = StrSave("-");
14493 gameInfo.white = StrSave(first.tidy);
14494 gameInfo.black = StrSave(UserName());
14495 gameInfo.timeControl = TimeControlTagValue();
14498 case MachinePlaysBlack:
14499 gameInfo.event = StrSave( appData.pgnEventHeader );
14500 gameInfo.site = StrSave(HostName());
14501 gameInfo.date = PGNDate();
14502 gameInfo.round = StrSave("-");
14503 gameInfo.white = StrSave(UserName());
14504 gameInfo.black = StrSave(first.tidy);
14505 gameInfo.timeControl = TimeControlTagValue();
14508 case TwoMachinesPlay:
14509 gameInfo.event = StrSave( appData.pgnEventHeader );
14510 gameInfo.site = StrSave(HostName());
14511 gameInfo.date = PGNDate();
14514 snprintf(buf, MSG_SIZ, "%d", roundNr);
14515 gameInfo.round = StrSave(buf);
14517 gameInfo.round = StrSave("-");
14519 if (first.twoMachinesColor[0] == 'w') {
14520 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14521 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14523 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14524 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14526 gameInfo.timeControl = TimeControlTagValue();
14530 gameInfo.event = StrSave("Edited game");
14531 gameInfo.site = StrSave(HostName());
14532 gameInfo.date = PGNDate();
14533 gameInfo.round = StrSave("-");
14534 gameInfo.white = StrSave("-");
14535 gameInfo.black = StrSave("-");
14536 gameInfo.result = r;
14537 gameInfo.resultDetails = p;
14541 gameInfo.event = StrSave("Edited position");
14542 gameInfo.site = StrSave(HostName());
14543 gameInfo.date = PGNDate();
14544 gameInfo.round = StrSave("-");
14545 gameInfo.white = StrSave("-");
14546 gameInfo.black = StrSave("-");
14549 case IcsPlayingWhite:
14550 case IcsPlayingBlack:
14555 case PlayFromGameFile:
14556 gameInfo.event = StrSave("Game from non-PGN file");
14557 gameInfo.site = StrSave(HostName());
14558 gameInfo.date = PGNDate();
14559 gameInfo.round = StrSave("-");
14560 gameInfo.white = StrSave("?");
14561 gameInfo.black = StrSave("?");
14570 ReplaceComment(index, text)
14578 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14579 pvInfoList[index-1].depth == len &&
14580 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14581 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14582 while (*text == '\n') text++;
14583 len = strlen(text);
14584 while (len > 0 && text[len - 1] == '\n') len--;
14586 if (commentList[index] != NULL)
14587 free(commentList[index]);
14590 commentList[index] = NULL;
14593 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14594 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14595 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14596 commentList[index] = (char *) malloc(len + 2);
14597 strncpy(commentList[index], text, len);
14598 commentList[index][len] = '\n';
14599 commentList[index][len + 1] = NULLCHAR;
14601 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14603 commentList[index] = (char *) malloc(len + 7);
14604 safeStrCpy(commentList[index], "{\n", 3);
14605 safeStrCpy(commentList[index]+2, text, len+1);
14606 commentList[index][len+2] = NULLCHAR;
14607 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14608 strcat(commentList[index], "\n}\n");
14622 if (ch == '\r') continue;
14624 } while (ch != '\0');
14628 AppendComment(index, text, addBraces)
14631 Boolean addBraces; // [HGM] braces: tells if we should add {}
14636 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14637 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14640 while (*text == '\n') text++;
14641 len = strlen(text);
14642 while (len > 0 && text[len - 1] == '\n') len--;
14644 if (len == 0) return;
14646 if (commentList[index] != NULL) {
14647 Boolean addClosingBrace = addBraces;
14648 old = commentList[index];
14649 oldlen = strlen(old);
14650 while(commentList[index][oldlen-1] == '\n')
14651 commentList[index][--oldlen] = NULLCHAR;
14652 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14653 safeStrCpy(commentList[index], old, oldlen + len + 6);
14655 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14656 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14657 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14658 while (*text == '\n') { text++; len--; }
14659 commentList[index][--oldlen] = NULLCHAR;
14661 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14662 else strcat(commentList[index], "\n");
14663 strcat(commentList[index], text);
14664 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14665 else strcat(commentList[index], "\n");
14667 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14669 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14670 else commentList[index][0] = NULLCHAR;
14671 strcat(commentList[index], text);
14672 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14673 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14677 static char * FindStr( char * text, char * sub_text )
14679 char * result = strstr( text, sub_text );
14681 if( result != NULL ) {
14682 result += strlen( sub_text );
14688 /* [AS] Try to extract PV info from PGN comment */
14689 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14690 char *GetInfoFromComment( int index, char * text )
14692 char * sep = text, *p;
14694 if( text != NULL && index > 0 ) {
14697 int time = -1, sec = 0, deci;
14698 char * s_eval = FindStr( text, "[%eval " );
14699 char * s_emt = FindStr( text, "[%emt " );
14701 if( s_eval != NULL || s_emt != NULL ) {
14705 if( s_eval != NULL ) {
14706 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14710 if( delim != ']' ) {
14715 if( s_emt != NULL ) {
14720 /* We expect something like: [+|-]nnn.nn/dd */
14723 if(*text != '{') return text; // [HGM] braces: must be normal comment
14725 sep = strchr( text, '/' );
14726 if( sep == NULL || sep < (text+4) ) {
14731 if(p[1] == '(') { // comment starts with PV
14732 p = strchr(p, ')'); // locate end of PV
14733 if(p == NULL || sep < p+5) return text;
14734 // at this point we have something like "{(.*) +0.23/6 ..."
14735 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14736 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14737 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14739 time = -1; sec = -1; deci = -1;
14740 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14741 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14742 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14743 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14747 if( score_lo < 0 || score_lo >= 100 ) {
14751 if(sec >= 0) time = 600*time + 10*sec; else
14752 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14754 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14756 /* [HGM] PV time: now locate end of PV info */
14757 while( *++sep >= '0' && *sep <= '9'); // strip depth
14759 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14761 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14763 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14764 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14775 pvInfoList[index-1].depth = depth;
14776 pvInfoList[index-1].score = score;
14777 pvInfoList[index-1].time = 10*time; // centi-sec
14778 if(*sep == '}') *sep = 0; else *--sep = '{';
14779 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14785 SendToProgram(message, cps)
14787 ChessProgramState *cps;
14789 int count, outCount, error;
14792 if (cps->pr == NULL) return;
14795 if (appData.debugMode) {
14798 fprintf(debugFP, "%ld >%-6s: %s",
14799 SubtractTimeMarks(&now, &programStartTime),
14800 cps->which, message);
14803 count = strlen(message);
14804 outCount = OutputToProcess(cps->pr, message, count, &error);
14805 if (outCount < count && !exiting
14806 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14807 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14808 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14809 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14810 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14811 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14812 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14813 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14815 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14816 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14817 gameInfo.result = res;
14819 gameInfo.resultDetails = StrSave(buf);
14821 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14822 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14827 ReceiveFromProgram(isr, closure, message, count, error)
14828 InputSourceRef isr;
14836 ChessProgramState *cps = (ChessProgramState *)closure;
14838 if (isr != cps->isr) return; /* Killed intentionally */
14841 RemoveInputSource(cps->isr);
14842 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14843 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14844 _(cps->which), cps->program);
14845 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14846 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14847 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14848 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14849 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14851 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14852 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14853 gameInfo.result = res;
14855 gameInfo.resultDetails = StrSave(buf);
14857 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14858 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14860 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14861 _(cps->which), cps->program);
14862 RemoveInputSource(cps->isr);
14864 /* [AS] Program is misbehaving badly... kill it */
14865 if( count == -2 ) {
14866 DestroyChildProcess( cps->pr, 9 );
14870 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14875 if ((end_str = strchr(message, '\r')) != NULL)
14876 *end_str = NULLCHAR;
14877 if ((end_str = strchr(message, '\n')) != NULL)
14878 *end_str = NULLCHAR;
14880 if (appData.debugMode) {
14881 TimeMark now; int print = 1;
14882 char *quote = ""; char c; int i;
14884 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14885 char start = message[0];
14886 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14887 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14888 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14889 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14890 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14891 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14892 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14893 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14894 sscanf(message, "hint: %c", &c)!=1 &&
14895 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14896 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14897 print = (appData.engineComments >= 2);
14899 message[0] = start; // restore original message
14903 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14904 SubtractTimeMarks(&now, &programStartTime), cps->which,
14910 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14911 if (appData.icsEngineAnalyze) {
14912 if (strstr(message, "whisper") != NULL ||
14913 strstr(message, "kibitz") != NULL ||
14914 strstr(message, "tellics") != NULL) return;
14917 HandleMachineMove(message, cps);
14922 SendTimeControl(cps, mps, tc, inc, sd, st)
14923 ChessProgramState *cps;
14924 int mps, inc, sd, st;
14930 if( timeControl_2 > 0 ) {
14931 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14932 tc = timeControl_2;
14935 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14936 inc /= cps->timeOdds;
14937 st /= cps->timeOdds;
14939 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14942 /* Set exact time per move, normally using st command */
14943 if (cps->stKludge) {
14944 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14946 if (seconds == 0) {
14947 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14949 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14952 snprintf(buf, MSG_SIZ, "st %d\n", st);
14955 /* Set conventional or incremental time control, using level command */
14956 if (seconds == 0) {
14957 /* Note old gnuchess bug -- minutes:seconds used to not work.
14958 Fixed in later versions, but still avoid :seconds
14959 when seconds is 0. */
14960 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14962 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14963 seconds, inc/1000.);
14966 SendToProgram(buf, cps);
14968 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14969 /* Orthogonally, limit search to given depth */
14971 if (cps->sdKludge) {
14972 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14974 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14976 SendToProgram(buf, cps);
14979 if(cps->nps >= 0) { /* [HGM] nps */
14980 if(cps->supportsNPS == FALSE)
14981 cps->nps = -1; // don't use if engine explicitly says not supported!
14983 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14984 SendToProgram(buf, cps);
14989 ChessProgramState *WhitePlayer()
14990 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14992 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14993 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14999 SendTimeRemaining(cps, machineWhite)
15000 ChessProgramState *cps;
15001 int /*boolean*/ machineWhite;
15003 char message[MSG_SIZ];
15006 /* Note: this routine must be called when the clocks are stopped
15007 or when they have *just* been set or switched; otherwise
15008 it will be off by the time since the current tick started.
15010 if (machineWhite) {
15011 time = whiteTimeRemaining / 10;
15012 otime = blackTimeRemaining / 10;
15014 time = blackTimeRemaining / 10;
15015 otime = whiteTimeRemaining / 10;
15017 /* [HGM] translate opponent's time by time-odds factor */
15018 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15019 if (appData.debugMode) {
15020 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15023 if (time <= 0) time = 1;
15024 if (otime <= 0) otime = 1;
15026 snprintf(message, MSG_SIZ, "time %ld\n", time);
15027 SendToProgram(message, cps);
15029 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15030 SendToProgram(message, cps);
15034 BoolFeature(p, name, loc, cps)
15038 ChessProgramState *cps;
15041 int len = strlen(name);
15044 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15046 sscanf(*p, "%d", &val);
15048 while (**p && **p != ' ')
15050 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15051 SendToProgram(buf, cps);
15058 IntFeature(p, name, loc, cps)
15062 ChessProgramState *cps;
15065 int len = strlen(name);
15066 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15068 sscanf(*p, "%d", loc);
15069 while (**p && **p != ' ') (*p)++;
15070 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15071 SendToProgram(buf, cps);
15078 StringFeature(p, name, loc, cps)
15082 ChessProgramState *cps;
15085 int len = strlen(name);
15086 if (strncmp((*p), name, len) == 0
15087 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15089 sscanf(*p, "%[^\"]", loc);
15090 while (**p && **p != '\"') (*p)++;
15091 if (**p == '\"') (*p)++;
15092 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15093 SendToProgram(buf, cps);
15100 ParseOption(Option *opt, ChessProgramState *cps)
15101 // [HGM] options: process the string that defines an engine option, and determine
15102 // name, type, default value, and allowed value range
15104 char *p, *q, buf[MSG_SIZ];
15105 int n, min = (-1)<<31, max = 1<<31, def;
15107 if(p = strstr(opt->name, " -spin ")) {
15108 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15109 if(max < min) max = min; // enforce consistency
15110 if(def < min) def = min;
15111 if(def > max) def = max;
15116 } else if((p = strstr(opt->name, " -slider "))) {
15117 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15118 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15119 if(max < min) max = min; // enforce consistency
15120 if(def < min) def = min;
15121 if(def > max) def = max;
15125 opt->type = Spin; // Slider;
15126 } else if((p = strstr(opt->name, " -string "))) {
15127 opt->textValue = p+9;
15128 opt->type = TextBox;
15129 } else if((p = strstr(opt->name, " -file "))) {
15130 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15131 opt->textValue = p+7;
15132 opt->type = FileName; // FileName;
15133 } else if((p = strstr(opt->name, " -path "))) {
15134 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15135 opt->textValue = p+7;
15136 opt->type = PathName; // PathName;
15137 } else if(p = strstr(opt->name, " -check ")) {
15138 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15139 opt->value = (def != 0);
15140 opt->type = CheckBox;
15141 } else if(p = strstr(opt->name, " -combo ")) {
15142 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15143 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15144 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15145 opt->value = n = 0;
15146 while(q = StrStr(q, " /// ")) {
15147 n++; *q = 0; // count choices, and null-terminate each of them
15149 if(*q == '*') { // remember default, which is marked with * prefix
15153 cps->comboList[cps->comboCnt++] = q;
15155 cps->comboList[cps->comboCnt++] = NULL;
15157 opt->type = ComboBox;
15158 } else if(p = strstr(opt->name, " -button")) {
15159 opt->type = Button;
15160 } else if(p = strstr(opt->name, " -save")) {
15161 opt->type = SaveButton;
15162 } else return FALSE;
15163 *p = 0; // terminate option name
15164 // now look if the command-line options define a setting for this engine option.
15165 if(cps->optionSettings && cps->optionSettings[0])
15166 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15167 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15168 snprintf(buf, MSG_SIZ, "option %s", p);
15169 if(p = strstr(buf, ",")) *p = 0;
15170 if(q = strchr(buf, '=')) switch(opt->type) {
15172 for(n=0; n<opt->max; n++)
15173 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15176 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15180 opt->value = atoi(q+1);
15185 SendToProgram(buf, cps);
15191 FeatureDone(cps, val)
15192 ChessProgramState* cps;
15195 DelayedEventCallback cb = GetDelayedEvent();
15196 if ((cb == InitBackEnd3 && cps == &first) ||
15197 (cb == SettingsMenuIfReady && cps == &second) ||
15198 (cb == LoadEngine) ||
15199 (cb == TwoMachinesEventIfReady)) {
15200 CancelDelayedEvent();
15201 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15203 cps->initDone = val;
15206 /* Parse feature command from engine */
15208 ParseFeatures(args, cps)
15210 ChessProgramState *cps;
15218 while (*p == ' ') p++;
15219 if (*p == NULLCHAR) return;
15221 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15222 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15223 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15224 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15225 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15226 if (BoolFeature(&p, "reuse", &val, cps)) {
15227 /* Engine can disable reuse, but can't enable it if user said no */
15228 if (!val) cps->reuse = FALSE;
15231 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15232 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15233 if (gameMode == TwoMachinesPlay) {
15234 DisplayTwoMachinesTitle();
15240 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15241 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15242 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15243 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15244 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15245 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15246 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15247 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15248 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15249 if (IntFeature(&p, "done", &val, cps)) {
15250 FeatureDone(cps, val);
15253 /* Added by Tord: */
15254 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15255 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15256 /* End of additions by Tord */
15258 /* [HGM] added features: */
15259 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15260 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15261 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15262 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15263 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15264 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15265 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15266 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15267 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15268 SendToProgram(buf, cps);
15271 if(cps->nrOptions >= MAX_OPTIONS) {
15273 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15274 DisplayError(buf, 0);
15278 /* End of additions by HGM */
15280 /* unknown feature: complain and skip */
15282 while (*q && *q != '=') q++;
15283 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15284 SendToProgram(buf, cps);
15290 while (*p && *p != '\"') p++;
15291 if (*p == '\"') p++;
15293 while (*p && *p != ' ') p++;
15301 PeriodicUpdatesEvent(newState)
15304 if (newState == appData.periodicUpdates)
15307 appData.periodicUpdates=newState;
15309 /* Display type changes, so update it now */
15310 // DisplayAnalysis();
15312 /* Get the ball rolling again... */
15314 AnalysisPeriodicEvent(1);
15315 StartAnalysisClock();
15320 PonderNextMoveEvent(newState)
15323 if (newState == appData.ponderNextMove) return;
15324 if (gameMode == EditPosition) EditPositionDone(TRUE);
15326 SendToProgram("hard\n", &first);
15327 if (gameMode == TwoMachinesPlay) {
15328 SendToProgram("hard\n", &second);
15331 SendToProgram("easy\n", &first);
15332 thinkOutput[0] = NULLCHAR;
15333 if (gameMode == TwoMachinesPlay) {
15334 SendToProgram("easy\n", &second);
15337 appData.ponderNextMove = newState;
15341 NewSettingEvent(option, feature, command, value)
15343 int option, value, *feature;
15347 if (gameMode == EditPosition) EditPositionDone(TRUE);
15348 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15349 if(feature == NULL || *feature) SendToProgram(buf, &first);
15350 if (gameMode == TwoMachinesPlay) {
15351 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15356 ShowThinkingEvent()
15357 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15359 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15360 int newState = appData.showThinking
15361 // [HGM] thinking: other features now need thinking output as well
15362 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15364 if (oldState == newState) return;
15365 oldState = newState;
15366 if (gameMode == EditPosition) EditPositionDone(TRUE);
15368 SendToProgram("post\n", &first);
15369 if (gameMode == TwoMachinesPlay) {
15370 SendToProgram("post\n", &second);
15373 SendToProgram("nopost\n", &first);
15374 thinkOutput[0] = NULLCHAR;
15375 if (gameMode == TwoMachinesPlay) {
15376 SendToProgram("nopost\n", &second);
15379 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15383 AskQuestionEvent(title, question, replyPrefix, which)
15384 char *title; char *question; char *replyPrefix; char *which;
15386 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15387 if (pr == NoProc) return;
15388 AskQuestion(title, question, replyPrefix, pr);
15392 TypeInEvent(char firstChar)
15394 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15395 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15396 gameMode == AnalyzeMode || gameMode == EditGame ||
15397 gameMode == EditPosition || gameMode == IcsExamining ||
15398 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15399 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15400 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15401 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15402 gameMode == Training) PopUpMoveDialog(firstChar);
15406 TypeInDoneEvent(char *move)
15409 int n, fromX, fromY, toX, toY;
15411 ChessMove moveType;
15414 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15415 EditPositionPasteFEN(move);
15418 // [HGM] movenum: allow move number to be typed in any mode
15419 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15424 if (gameMode != EditGame && currentMove != forwardMostMove &&
15425 gameMode != Training) {
15426 DisplayMoveError(_("Displayed move is not current"));
15428 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15429 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15430 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15431 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15432 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15433 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15435 DisplayMoveError(_("Could not parse move"));
15441 DisplayMove(moveNumber)
15444 char message[MSG_SIZ];
15446 char cpThinkOutput[MSG_SIZ];
15448 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15450 if (moveNumber == forwardMostMove - 1 ||
15451 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15453 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15455 if (strchr(cpThinkOutput, '\n')) {
15456 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15459 *cpThinkOutput = NULLCHAR;
15462 /* [AS] Hide thinking from human user */
15463 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15464 *cpThinkOutput = NULLCHAR;
15465 if( thinkOutput[0] != NULLCHAR ) {
15468 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15469 cpThinkOutput[i] = '.';
15471 cpThinkOutput[i] = NULLCHAR;
15472 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15476 if (moveNumber == forwardMostMove - 1 &&
15477 gameInfo.resultDetails != NULL) {
15478 if (gameInfo.resultDetails[0] == NULLCHAR) {
15479 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15481 snprintf(res, MSG_SIZ, " {%s} %s",
15482 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15488 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15489 DisplayMessage(res, cpThinkOutput);
15491 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15492 WhiteOnMove(moveNumber) ? " " : ".. ",
15493 parseList[moveNumber], res);
15494 DisplayMessage(message, cpThinkOutput);
15499 DisplayComment(moveNumber, text)
15503 char title[MSG_SIZ];
15505 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15506 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15508 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15509 WhiteOnMove(moveNumber) ? " " : ".. ",
15510 parseList[moveNumber]);
15512 if (text != NULL && (appData.autoDisplayComment || commentUp))
15513 CommentPopUp(title, text);
15516 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15517 * might be busy thinking or pondering. It can be omitted if your
15518 * gnuchess is configured to stop thinking immediately on any user
15519 * input. However, that gnuchess feature depends on the FIONREAD
15520 * ioctl, which does not work properly on some flavors of Unix.
15524 ChessProgramState *cps;
15527 if (!cps->useSigint) return;
15528 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15529 switch (gameMode) {
15530 case MachinePlaysWhite:
15531 case MachinePlaysBlack:
15532 case TwoMachinesPlay:
15533 case IcsPlayingWhite:
15534 case IcsPlayingBlack:
15537 /* Skip if we know it isn't thinking */
15538 if (!cps->maybeThinking) return;
15539 if (appData.debugMode)
15540 fprintf(debugFP, "Interrupting %s\n", cps->which);
15541 InterruptChildProcess(cps->pr);
15542 cps->maybeThinking = FALSE;
15547 #endif /*ATTENTION*/
15553 if (whiteTimeRemaining <= 0) {
15556 if (appData.icsActive) {
15557 if (appData.autoCallFlag &&
15558 gameMode == IcsPlayingBlack && !blackFlag) {
15559 SendToICS(ics_prefix);
15560 SendToICS("flag\n");
15564 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15566 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15567 if (appData.autoCallFlag) {
15568 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15575 if (blackTimeRemaining <= 0) {
15578 if (appData.icsActive) {
15579 if (appData.autoCallFlag &&
15580 gameMode == IcsPlayingWhite && !whiteFlag) {
15581 SendToICS(ics_prefix);
15582 SendToICS("flag\n");
15586 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15588 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15589 if (appData.autoCallFlag) {
15590 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15603 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15604 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15607 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15609 if ( !WhiteOnMove(forwardMostMove) ) {
15610 /* White made time control */
15611 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15612 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15613 /* [HGM] time odds: correct new time quota for time odds! */
15614 / WhitePlayer()->timeOdds;
15615 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15617 lastBlack -= blackTimeRemaining;
15618 /* Black made time control */
15619 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15620 / WhitePlayer()->other->timeOdds;
15621 lastWhite = whiteTimeRemaining;
15626 DisplayBothClocks()
15628 int wom = gameMode == EditPosition ?
15629 !blackPlaysFirst : WhiteOnMove(currentMove);
15630 DisplayWhiteClock(whiteTimeRemaining, wom);
15631 DisplayBlackClock(blackTimeRemaining, !wom);
15635 /* Timekeeping seems to be a portability nightmare. I think everyone
15636 has ftime(), but I'm really not sure, so I'm including some ifdefs
15637 to use other calls if you don't. Clocks will be less accurate if
15638 you have neither ftime nor gettimeofday.
15641 /* VS 2008 requires the #include outside of the function */
15642 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15643 #include <sys/timeb.h>
15646 /* Get the current time as a TimeMark */
15651 #if HAVE_GETTIMEOFDAY
15653 struct timeval timeVal;
15654 struct timezone timeZone;
15656 gettimeofday(&timeVal, &timeZone);
15657 tm->sec = (long) timeVal.tv_sec;
15658 tm->ms = (int) (timeVal.tv_usec / 1000L);
15660 #else /*!HAVE_GETTIMEOFDAY*/
15663 // include <sys/timeb.h> / moved to just above start of function
15664 struct timeb timeB;
15667 tm->sec = (long) timeB.time;
15668 tm->ms = (int) timeB.millitm;
15670 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15671 tm->sec = (long) time(NULL);
15677 /* Return the difference in milliseconds between two
15678 time marks. We assume the difference will fit in a long!
15681 SubtractTimeMarks(tm2, tm1)
15682 TimeMark *tm2, *tm1;
15684 return 1000L*(tm2->sec - tm1->sec) +
15685 (long) (tm2->ms - tm1->ms);
15690 * Code to manage the game clocks.
15692 * In tournament play, black starts the clock and then white makes a move.
15693 * We give the human user a slight advantage if he is playing white---the
15694 * clocks don't run until he makes his first move, so it takes zero time.
15695 * Also, we don't account for network lag, so we could get out of sync
15696 * with GNU Chess's clock -- but then, referees are always right.
15699 static TimeMark tickStartTM;
15700 static long intendedTickLength;
15703 NextTickLength(timeRemaining)
15704 long timeRemaining;
15706 long nominalTickLength, nextTickLength;
15708 if (timeRemaining > 0L && timeRemaining <= 10000L)
15709 nominalTickLength = 100L;
15711 nominalTickLength = 1000L;
15712 nextTickLength = timeRemaining % nominalTickLength;
15713 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15715 return nextTickLength;
15718 /* Adjust clock one minute up or down */
15720 AdjustClock(Boolean which, int dir)
15722 if(which) blackTimeRemaining += 60000*dir;
15723 else whiteTimeRemaining += 60000*dir;
15724 DisplayBothClocks();
15727 /* Stop clocks and reset to a fresh time control */
15731 (void) StopClockTimer();
15732 if (appData.icsActive) {
15733 whiteTimeRemaining = blackTimeRemaining = 0;
15734 } else if (searchTime) {
15735 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15736 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15737 } else { /* [HGM] correct new time quote for time odds */
15738 whiteTC = blackTC = fullTimeControlString;
15739 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15740 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15742 if (whiteFlag || blackFlag) {
15744 whiteFlag = blackFlag = FALSE;
15746 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15747 DisplayBothClocks();
15750 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15752 /* Decrement running clock by amount of time that has passed */
15756 long timeRemaining;
15757 long lastTickLength, fudge;
15760 if (!appData.clockMode) return;
15761 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15765 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15767 /* Fudge if we woke up a little too soon */
15768 fudge = intendedTickLength - lastTickLength;
15769 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15771 if (WhiteOnMove(forwardMostMove)) {
15772 if(whiteNPS >= 0) lastTickLength = 0;
15773 timeRemaining = whiteTimeRemaining -= lastTickLength;
15774 if(timeRemaining < 0 && !appData.icsActive) {
15775 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15776 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15777 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15778 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15781 DisplayWhiteClock(whiteTimeRemaining - fudge,
15782 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15784 if(blackNPS >= 0) lastTickLength = 0;
15785 timeRemaining = blackTimeRemaining -= lastTickLength;
15786 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15787 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15789 blackStartMove = forwardMostMove;
15790 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15793 DisplayBlackClock(blackTimeRemaining - fudge,
15794 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15796 if (CheckFlags()) return;
15799 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15800 StartClockTimer(intendedTickLength);
15802 /* if the time remaining has fallen below the alarm threshold, sound the
15803 * alarm. if the alarm has sounded and (due to a takeback or time control
15804 * with increment) the time remaining has increased to a level above the
15805 * threshold, reset the alarm so it can sound again.
15808 if (appData.icsActive && appData.icsAlarm) {
15810 /* make sure we are dealing with the user's clock */
15811 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15812 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15815 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15816 alarmSounded = FALSE;
15817 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15819 alarmSounded = TRUE;
15825 /* A player has just moved, so stop the previously running
15826 clock and (if in clock mode) start the other one.
15827 We redisplay both clocks in case we're in ICS mode, because
15828 ICS gives us an update to both clocks after every move.
15829 Note that this routine is called *after* forwardMostMove
15830 is updated, so the last fractional tick must be subtracted
15831 from the color that is *not* on move now.
15834 SwitchClocks(int newMoveNr)
15836 long lastTickLength;
15838 int flagged = FALSE;
15842 if (StopClockTimer() && appData.clockMode) {
15843 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15844 if (!WhiteOnMove(forwardMostMove)) {
15845 if(blackNPS >= 0) lastTickLength = 0;
15846 blackTimeRemaining -= lastTickLength;
15847 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15848 // if(pvInfoList[forwardMostMove].time == -1)
15849 pvInfoList[forwardMostMove].time = // use GUI time
15850 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15852 if(whiteNPS >= 0) lastTickLength = 0;
15853 whiteTimeRemaining -= lastTickLength;
15854 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15855 // if(pvInfoList[forwardMostMove].time == -1)
15856 pvInfoList[forwardMostMove].time =
15857 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15859 flagged = CheckFlags();
15861 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15862 CheckTimeControl();
15864 if (flagged || !appData.clockMode) return;
15866 switch (gameMode) {
15867 case MachinePlaysBlack:
15868 case MachinePlaysWhite:
15869 case BeginningOfGame:
15870 if (pausing) return;
15874 case PlayFromGameFile:
15882 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15883 if(WhiteOnMove(forwardMostMove))
15884 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15885 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15889 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15890 whiteTimeRemaining : blackTimeRemaining);
15891 StartClockTimer(intendedTickLength);
15895 /* Stop both clocks */
15899 long lastTickLength;
15902 if (!StopClockTimer()) return;
15903 if (!appData.clockMode) return;
15907 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15908 if (WhiteOnMove(forwardMostMove)) {
15909 if(whiteNPS >= 0) lastTickLength = 0;
15910 whiteTimeRemaining -= lastTickLength;
15911 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15913 if(blackNPS >= 0) lastTickLength = 0;
15914 blackTimeRemaining -= lastTickLength;
15915 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15920 /* Start clock of player on move. Time may have been reset, so
15921 if clock is already running, stop and restart it. */
15925 (void) StopClockTimer(); /* in case it was running already */
15926 DisplayBothClocks();
15927 if (CheckFlags()) return;
15929 if (!appData.clockMode) return;
15930 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15932 GetTimeMark(&tickStartTM);
15933 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15934 whiteTimeRemaining : blackTimeRemaining);
15936 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15937 whiteNPS = blackNPS = -1;
15938 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15939 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15940 whiteNPS = first.nps;
15941 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15942 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15943 blackNPS = first.nps;
15944 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15945 whiteNPS = second.nps;
15946 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15947 blackNPS = second.nps;
15948 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15950 StartClockTimer(intendedTickLength);
15957 long second, minute, hour, day;
15959 static char buf[32];
15961 if (ms > 0 && ms <= 9900) {
15962 /* convert milliseconds to tenths, rounding up */
15963 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15965 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15969 /* convert milliseconds to seconds, rounding up */
15970 /* use floating point to avoid strangeness of integer division
15971 with negative dividends on many machines */
15972 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15979 day = second / (60 * 60 * 24);
15980 second = second % (60 * 60 * 24);
15981 hour = second / (60 * 60);
15982 second = second % (60 * 60);
15983 minute = second / 60;
15984 second = second % 60;
15987 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15988 sign, day, hour, minute, second);
15990 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15992 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15999 * This is necessary because some C libraries aren't ANSI C compliant yet.
16002 StrStr(string, match)
16003 char *string, *match;
16007 length = strlen(match);
16009 for (i = strlen(string) - length; i >= 0; i--, string++)
16010 if (!strncmp(match, string, length))
16017 StrCaseStr(string, match)
16018 char *string, *match;
16022 length = strlen(match);
16024 for (i = strlen(string) - length; i >= 0; i--, string++) {
16025 for (j = 0; j < length; j++) {
16026 if (ToLower(match[j]) != ToLower(string[j]))
16029 if (j == length) return string;
16043 c1 = ToLower(*s1++);
16044 c2 = ToLower(*s2++);
16045 if (c1 > c2) return 1;
16046 if (c1 < c2) return -1;
16047 if (c1 == NULLCHAR) return 0;
16056 return isupper(c) ? tolower(c) : c;
16064 return islower(c) ? toupper(c) : c;
16066 #endif /* !_amigados */
16074 if ((ret = (char *) malloc(strlen(s) + 1)))
16076 safeStrCpy(ret, s, strlen(s)+1);
16082 StrSavePtr(s, savePtr)
16083 char *s, **savePtr;
16088 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16089 safeStrCpy(*savePtr, s, strlen(s)+1);
16101 clock = time((time_t *)NULL);
16102 tm = localtime(&clock);
16103 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16104 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16105 return StrSave(buf);
16110 PositionToFEN(move, overrideCastling)
16112 char *overrideCastling;
16114 int i, j, fromX, fromY, toX, toY;
16121 whiteToPlay = (gameMode == EditPosition) ?
16122 !blackPlaysFirst : (move % 2 == 0);
16125 /* Piece placement data */
16126 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16127 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16129 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16130 if (boards[move][i][j] == EmptySquare) {
16132 } else { ChessSquare piece = boards[move][i][j];
16133 if (emptycount > 0) {
16134 if(emptycount<10) /* [HGM] can be >= 10 */
16135 *p++ = '0' + emptycount;
16136 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16139 if(PieceToChar(piece) == '+') {
16140 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16142 piece = (ChessSquare)(DEMOTED piece);
16144 *p++ = PieceToChar(piece);
16146 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16147 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16152 if (emptycount > 0) {
16153 if(emptycount<10) /* [HGM] can be >= 10 */
16154 *p++ = '0' + emptycount;
16155 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16162 /* [HGM] print Crazyhouse or Shogi holdings */
16163 if( gameInfo.holdingsWidth ) {
16164 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16166 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16167 piece = boards[move][i][BOARD_WIDTH-1];
16168 if( piece != EmptySquare )
16169 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16170 *p++ = PieceToChar(piece);
16172 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16173 piece = boards[move][BOARD_HEIGHT-i-1][0];
16174 if( piece != EmptySquare )
16175 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16176 *p++ = PieceToChar(piece);
16179 if( q == p ) *p++ = '-';
16185 *p++ = whiteToPlay ? 'w' : 'b';
16188 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16189 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16191 if(nrCastlingRights) {
16193 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16194 /* [HGM] write directly from rights */
16195 if(boards[move][CASTLING][2] != NoRights &&
16196 boards[move][CASTLING][0] != NoRights )
16197 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16198 if(boards[move][CASTLING][2] != NoRights &&
16199 boards[move][CASTLING][1] != NoRights )
16200 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16201 if(boards[move][CASTLING][5] != NoRights &&
16202 boards[move][CASTLING][3] != NoRights )
16203 *p++ = boards[move][CASTLING][3] + AAA;
16204 if(boards[move][CASTLING][5] != NoRights &&
16205 boards[move][CASTLING][4] != NoRights )
16206 *p++ = boards[move][CASTLING][4] + AAA;
16209 /* [HGM] write true castling rights */
16210 if( nrCastlingRights == 6 ) {
16211 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16212 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16213 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16214 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16215 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16216 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16217 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16218 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16221 if (q == p) *p++ = '-'; /* No castling rights */
16225 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16226 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16227 /* En passant target square */
16228 if (move > backwardMostMove) {
16229 fromX = moveList[move - 1][0] - AAA;
16230 fromY = moveList[move - 1][1] - ONE;
16231 toX = moveList[move - 1][2] - AAA;
16232 toY = moveList[move - 1][3] - ONE;
16233 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16234 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16235 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16237 /* 2-square pawn move just happened */
16239 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16243 } else if(move == backwardMostMove) {
16244 // [HGM] perhaps we should always do it like this, and forget the above?
16245 if((signed char)boards[move][EP_STATUS] >= 0) {
16246 *p++ = boards[move][EP_STATUS] + AAA;
16247 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16258 /* [HGM] find reversible plies */
16259 { int i = 0, j=move;
16261 if (appData.debugMode) { int k;
16262 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16263 for(k=backwardMostMove; k<=forwardMostMove; k++)
16264 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16268 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16269 if( j == backwardMostMove ) i += initialRulePlies;
16270 sprintf(p, "%d ", i);
16271 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16273 /* Fullmove number */
16274 sprintf(p, "%d", (move / 2) + 1);
16276 return StrSave(buf);
16280 ParseFEN(board, blackPlaysFirst, fen)
16282 int *blackPlaysFirst;
16292 /* [HGM] by default clear Crazyhouse holdings, if present */
16293 if(gameInfo.holdingsWidth) {
16294 for(i=0; i<BOARD_HEIGHT; i++) {
16295 board[i][0] = EmptySquare; /* black holdings */
16296 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16297 board[i][1] = (ChessSquare) 0; /* black counts */
16298 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16302 /* Piece placement data */
16303 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16306 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16307 if (*p == '/') p++;
16308 emptycount = gameInfo.boardWidth - j;
16309 while (emptycount--)
16310 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16312 #if(BOARD_FILES >= 10)
16313 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16314 p++; emptycount=10;
16315 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16316 while (emptycount--)
16317 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16319 } else if (isdigit(*p)) {
16320 emptycount = *p++ - '0';
16321 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16322 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16323 while (emptycount--)
16324 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16325 } else if (*p == '+' || isalpha(*p)) {
16326 if (j >= gameInfo.boardWidth) return FALSE;
16328 piece = CharToPiece(*++p);
16329 if(piece == EmptySquare) return FALSE; /* unknown piece */
16330 piece = (ChessSquare) (PROMOTED piece ); p++;
16331 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16332 } else piece = CharToPiece(*p++);
16334 if(piece==EmptySquare) return FALSE; /* unknown piece */
16335 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16336 piece = (ChessSquare) (PROMOTED piece);
16337 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16340 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16346 while (*p == '/' || *p == ' ') p++;
16348 /* [HGM] look for Crazyhouse holdings here */
16349 while(*p==' ') p++;
16350 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16352 if(*p == '-' ) p++; /* empty holdings */ else {
16353 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16354 /* if we would allow FEN reading to set board size, we would */
16355 /* have to add holdings and shift the board read so far here */
16356 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16358 if((int) piece >= (int) BlackPawn ) {
16359 i = (int)piece - (int)BlackPawn;
16360 i = PieceToNumber((ChessSquare)i);
16361 if( i >= gameInfo.holdingsSize ) return FALSE;
16362 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16363 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16365 i = (int)piece - (int)WhitePawn;
16366 i = PieceToNumber((ChessSquare)i);
16367 if( i >= gameInfo.holdingsSize ) return FALSE;
16368 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16369 board[i][BOARD_WIDTH-2]++; /* black holdings */
16376 while(*p == ' ') p++;
16380 if(appData.colorNickNames) {
16381 if( c == appData.colorNickNames[0] ) c = 'w'; else
16382 if( c == appData.colorNickNames[1] ) c = 'b';
16386 *blackPlaysFirst = FALSE;
16389 *blackPlaysFirst = TRUE;
16395 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16396 /* return the extra info in global variiables */
16398 /* set defaults in case FEN is incomplete */
16399 board[EP_STATUS] = EP_UNKNOWN;
16400 for(i=0; i<nrCastlingRights; i++ ) {
16401 board[CASTLING][i] =
16402 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16403 } /* assume possible unless obviously impossible */
16404 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16405 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16406 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16407 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16408 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16409 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16410 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16411 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16414 while(*p==' ') p++;
16415 if(nrCastlingRights) {
16416 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16417 /* castling indicator present, so default becomes no castlings */
16418 for(i=0; i<nrCastlingRights; i++ ) {
16419 board[CASTLING][i] = NoRights;
16422 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16423 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16424 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16425 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16426 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16428 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16429 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16430 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16432 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16433 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16434 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16435 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16436 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16437 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16440 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16441 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16442 board[CASTLING][2] = whiteKingFile;
16445 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16446 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16447 board[CASTLING][2] = whiteKingFile;
16450 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16451 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16452 board[CASTLING][5] = blackKingFile;
16455 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16456 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16457 board[CASTLING][5] = blackKingFile;
16460 default: /* FRC castlings */
16461 if(c >= 'a') { /* black rights */
16462 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16463 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16464 if(i == BOARD_RGHT) break;
16465 board[CASTLING][5] = i;
16467 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16468 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16470 board[CASTLING][3] = c;
16472 board[CASTLING][4] = c;
16473 } else { /* white rights */
16474 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16475 if(board[0][i] == WhiteKing) break;
16476 if(i == BOARD_RGHT) break;
16477 board[CASTLING][2] = i;
16478 c -= AAA - 'a' + 'A';
16479 if(board[0][c] >= WhiteKing) break;
16481 board[CASTLING][0] = c;
16483 board[CASTLING][1] = c;
16487 for(i=0; i<nrCastlingRights; i++)
16488 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16489 if (appData.debugMode) {
16490 fprintf(debugFP, "FEN castling rights:");
16491 for(i=0; i<nrCastlingRights; i++)
16492 fprintf(debugFP, " %d", board[CASTLING][i]);
16493 fprintf(debugFP, "\n");
16496 while(*p==' ') p++;
16499 /* read e.p. field in games that know e.p. capture */
16500 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16501 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16503 p++; board[EP_STATUS] = EP_NONE;
16505 char c = *p++ - AAA;
16507 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16508 if(*p >= '0' && *p <='9') p++;
16509 board[EP_STATUS] = c;
16514 if(sscanf(p, "%d", &i) == 1) {
16515 FENrulePlies = i; /* 50-move ply counter */
16516 /* (The move number is still ignored) */
16523 EditPositionPasteFEN(char *fen)
16526 Board initial_position;
16528 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16529 DisplayError(_("Bad FEN position in clipboard"), 0);
16532 int savedBlackPlaysFirst = blackPlaysFirst;
16533 EditPositionEvent();
16534 blackPlaysFirst = savedBlackPlaysFirst;
16535 CopyBoard(boards[0], initial_position);
16536 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16537 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16538 DisplayBothClocks();
16539 DrawPosition(FALSE, boards[currentMove]);
16544 static char cseq[12] = "\\ ";
16546 Boolean set_cont_sequence(char *new_seq)
16551 // handle bad attempts to set the sequence
16553 return 0; // acceptable error - no debug
16555 len = strlen(new_seq);
16556 ret = (len > 0) && (len < sizeof(cseq));
16558 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16559 else if (appData.debugMode)
16560 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16565 reformat a source message so words don't cross the width boundary. internal
16566 newlines are not removed. returns the wrapped size (no null character unless
16567 included in source message). If dest is NULL, only calculate the size required
16568 for the dest buffer. lp argument indicats line position upon entry, and it's
16569 passed back upon exit.
16571 int wrap(char *dest, char *src, int count, int width, int *lp)
16573 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16575 cseq_len = strlen(cseq);
16576 old_line = line = *lp;
16577 ansi = len = clen = 0;
16579 for (i=0; i < count; i++)
16581 if (src[i] == '\033')
16584 // if we hit the width, back up
16585 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16587 // store i & len in case the word is too long
16588 old_i = i, old_len = len;
16590 // find the end of the last word
16591 while (i && src[i] != ' ' && src[i] != '\n')
16597 // word too long? restore i & len before splitting it
16598 if ((old_i-i+clen) >= width)
16605 if (i && src[i-1] == ' ')
16608 if (src[i] != ' ' && src[i] != '\n')
16615 // now append the newline and continuation sequence
16620 strncpy(dest+len, cseq, cseq_len);
16628 dest[len] = src[i];
16632 if (src[i] == '\n')
16637 if (dest && appData.debugMode)
16639 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16640 count, width, line, len, *lp);
16641 show_bytes(debugFP, src, count);
16642 fprintf(debugFP, "\ndest: ");
16643 show_bytes(debugFP, dest, len);
16644 fprintf(debugFP, "\n");
16646 *lp = dest ? line : old_line;
16651 // [HGM] vari: routines for shelving variations
16652 Boolean modeRestore = FALSE;
16655 PushInner(int firstMove, int lastMove)
16657 int i, j, nrMoves = lastMove - firstMove;
16659 // push current tail of game on stack
16660 savedResult[storedGames] = gameInfo.result;
16661 savedDetails[storedGames] = gameInfo.resultDetails;
16662 gameInfo.resultDetails = NULL;
16663 savedFirst[storedGames] = firstMove;
16664 savedLast [storedGames] = lastMove;
16665 savedFramePtr[storedGames] = framePtr;
16666 framePtr -= nrMoves; // reserve space for the boards
16667 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16668 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16669 for(j=0; j<MOVE_LEN; j++)
16670 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16671 for(j=0; j<2*MOVE_LEN; j++)
16672 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16673 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16674 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16675 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16676 pvInfoList[firstMove+i-1].depth = 0;
16677 commentList[framePtr+i] = commentList[firstMove+i];
16678 commentList[firstMove+i] = NULL;
16682 forwardMostMove = firstMove; // truncate game so we can start variation
16686 PushTail(int firstMove, int lastMove)
16688 if(appData.icsActive) { // only in local mode
16689 forwardMostMove = currentMove; // mimic old ICS behavior
16692 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16694 PushInner(firstMove, lastMove);
16695 if(storedGames == 1) GreyRevert(FALSE);
16696 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16700 PopInner(Boolean annotate)
16703 char buf[8000], moveBuf[20];
16705 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16706 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16707 nrMoves = savedLast[storedGames] - currentMove;
16710 if(!WhiteOnMove(currentMove))
16711 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16712 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16713 for(i=currentMove; i<forwardMostMove; i++) {
16715 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16716 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16717 strcat(buf, moveBuf);
16718 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16719 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16723 for(i=1; i<=nrMoves; i++) { // copy last variation back
16724 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16725 for(j=0; j<MOVE_LEN; j++)
16726 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16727 for(j=0; j<2*MOVE_LEN; j++)
16728 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16729 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16730 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16731 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16732 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16733 commentList[currentMove+i] = commentList[framePtr+i];
16734 commentList[framePtr+i] = NULL;
16736 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16737 framePtr = savedFramePtr[storedGames];
16738 gameInfo.result = savedResult[storedGames];
16739 if(gameInfo.resultDetails != NULL) {
16740 free(gameInfo.resultDetails);
16742 gameInfo.resultDetails = savedDetails[storedGames];
16743 forwardMostMove = currentMove + nrMoves;
16747 PopTail(Boolean annotate)
16749 if(appData.icsActive) return FALSE; // only in local mode
16750 if(!storedGames) return FALSE; // sanity
16751 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16753 PopInner(annotate);
16754 if(currentMove < forwardMostMove) ForwardEvent(); else
16755 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16757 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16763 { // remove all shelved variations
16765 for(i=0; i<storedGames; i++) {
16766 if(savedDetails[i])
16767 free(savedDetails[i]);
16768 savedDetails[i] = NULL;
16770 for(i=framePtr; i<MAX_MOVES; i++) {
16771 if(commentList[i]) free(commentList[i]);
16772 commentList[i] = NULL;
16774 framePtr = MAX_MOVES-1;
16779 LoadVariation(int index, char *text)
16780 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16781 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16782 int level = 0, move;
16784 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16785 // first find outermost bracketing variation
16786 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16787 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16788 if(*p == '{') wait = '}'; else
16789 if(*p == '[') wait = ']'; else
16790 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16791 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16793 if(*p == wait) wait = NULLCHAR; // closing ]} found
16796 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16797 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16798 end[1] = NULLCHAR; // clip off comment beyond variation
16799 ToNrEvent(currentMove-1);
16800 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16801 // kludge: use ParsePV() to append variation to game
16802 move = currentMove;
16803 ParsePV(start, TRUE, TRUE);
16804 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16805 ClearPremoveHighlights();
16807 ToNrEvent(currentMove+1);