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, 2012 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 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy (char *dst, const char *src, size_t count)
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble (u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second, pairing;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
455 int have_sent_ICS_logon = 0;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
507 ChessSquare FIDEArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackKnight, BlackRook }
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackKing, BlackKnight, BlackRook }
521 ChessSquare KnightmateArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524 { BlackRook, BlackMan, BlackBishop, BlackQueen,
525 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackMan, BlackFerz,
553 BlackKing, BlackMan, BlackKnight, BlackRook }
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating (char *str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine (ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions (ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
738 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 InitEngine (ChessProgramState *cps, int n)
748 { // [HGM] all engine initialiation put in a function that does one engine
752 cps->which = engineNames[n];
753 cps->maybeThinking = FALSE;
757 cps->sendDrawOffers = 1;
759 cps->program = appData.chessProgram[n];
760 cps->host = appData.host[n];
761 cps->dir = appData.directory[n];
762 cps->initString = appData.engInitString[n];
763 cps->computerString = appData.computerString[n];
764 cps->useSigint = TRUE;
765 cps->useSigterm = TRUE;
766 cps->reuse = appData.reuse[n];
767 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
768 cps->useSetboard = FALSE;
770 cps->usePing = FALSE;
773 cps->usePlayother = FALSE;
774 cps->useColors = TRUE;
775 cps->useUsermove = FALSE;
776 cps->sendICS = FALSE;
777 cps->sendName = appData.icsActive;
778 cps->sdKludge = FALSE;
779 cps->stKludge = FALSE;
780 TidyProgramName(cps->program, cps->host, cps->tidy);
782 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783 cps->analysisSupport = 2; /* detect */
784 cps->analyzing = FALSE;
785 cps->initDone = FALSE;
787 /* New features added by Tord: */
788 cps->useFEN960 = FALSE;
789 cps->useOOCastle = TRUE;
790 /* End of new features added by Tord. */
791 cps->fenOverride = appData.fenOverride[n];
793 /* [HGM] time odds: set factor for each machine */
794 cps->timeOdds = appData.timeOdds[n];
796 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797 cps->accumulateTC = appData.accumulateTC[n];
798 cps->maxNrOfSessions = 1;
803 cps->supportsNPS = UNKNOWN;
804 cps->memSize = FALSE;
805 cps->maxCores = FALSE;
806 cps->egtFormats[0] = NULLCHAR;
809 cps->optionSettings = appData.engOptions[n];
811 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812 cps->isUCI = appData.isUCI[n]; /* [AS] */
813 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815 if (appData.protocolVersion[n] > PROTOVER
816 || appData.protocolVersion[n] < 1)
821 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822 appData.protocolVersion[n]);
823 if( (len >= MSG_SIZ) && appData.debugMode )
824 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826 DisplayFatalError(buf, 0, 2);
830 cps->protocolVersion = appData.protocolVersion[n];
833 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ParseFeatures(appData.featureDefaults, cps);
837 ChessProgramState *savCps;
843 if(WaitForEngine(savCps, LoadEngine)) return;
844 CommonEngineInit(); // recalculate time odds
845 if(gameInfo.variant != StringToVariant(appData.variant)) {
846 // we changed variant when loading the engine; this forces us to reset
847 Reset(TRUE, savCps != &first);
848 EditGameEvent(); // for consistency with other path, as Reset changes mode
850 InitChessProgram(savCps, FALSE);
851 SendToProgram("force\n", savCps);
852 DisplayMessage("", "");
853 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
860 ReplaceEngine (ChessProgramState *cps, int n)
864 appData.noChessProgram = FALSE;
865 appData.clockMode = TRUE;
868 if(n) return; // only startup first engine immediately; second can wait
869 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876 static char resetOptions[] =
877 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883 FloatToFront(char **list, char *engineLine)
885 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887 if(appData.recentEngines <= 0) return;
888 TidyProgramName(engineLine, "localhost", tidy+1);
889 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890 strncpy(buf+1, *list, MSG_SIZ-50);
891 if(p = strstr(buf, tidy)) { // tidy name appears in list
892 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893 while(*p++ = *++q); // squeeze out
895 strcat(tidy, buf+1); // put list behind tidy name
896 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898 ASSIGN(*list, tidy+1);
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904 Load (ChessProgramState *cps, int i)
906 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912 appData.firstProtocolVersion = PROTOVER;
913 ParseArgsFromString(buf);
915 ReplaceEngine(cps, i);
916 FloatToFront(&appData.recentEngineList, engineLine);
920 while(q = strchr(p, SLASH)) p = q+1;
921 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922 if(engineDir[0] != NULLCHAR) {
923 ASSIGN(appData.directory[i], engineDir); p = engineName;
924 } else if(p != engineName) { // derive directory from engine path, when not given
926 ASSIGN(appData.directory[i], engineName);
928 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929 } else { ASSIGN(appData.directory[i], "."); }
931 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932 snprintf(command, MSG_SIZ, "%s %s", p, params);
935 ASSIGN(appData.chessProgram[i], p);
936 appData.isUCI[i] = isUCI;
937 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938 appData.hasOwnBookUCI[i] = hasBook;
939 if(!nickName[0]) useNick = FALSE;
940 if(useNick) ASSIGN(appData.pgnName[i], nickName);
944 q = firstChessProgramNames;
945 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948 quote, p, quote, appData.directory[i],
949 useNick ? " -fn \"" : "",
950 useNick ? nickName : "",
952 v1 ? " -firstProtocolVersion 1" : "",
953 hasBook ? "" : " -fNoOwnBookUCI",
954 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955 storeVariant ? " -variant " : "",
956 storeVariant ? VariantName(gameInfo.variant) : "");
957 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959 if(insert != q) insert[-1] = NULLCHAR;
960 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962 FloatToFront(&appData.recentEngineList, buf);
964 ReplaceEngine(cps, i);
970 int matched, min, sec;
972 * Parse timeControl resource
974 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975 appData.movesPerSession)) {
977 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978 DisplayFatalError(buf, 0, 2);
982 * Parse searchTime resource
984 if (*appData.searchTime != NULLCHAR) {
985 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987 searchTime = min * 60;
988 } else if (matched == 2) {
989 searchTime = min * 60 + sec;
992 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993 DisplayFatalError(buf, 0, 2);
1002 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005 GetTimeMark(&programStartTime);
1006 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007 appData.seedBase = random() + (random()<<15);
1008 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010 ClearProgramStats();
1011 programStats.ok_to_send = 1;
1012 programStats.seen_stat = 0;
1015 * Initialize game list
1021 * Internet chess server status
1023 if (appData.icsActive) {
1024 appData.matchMode = FALSE;
1025 appData.matchGames = 0;
1027 appData.noChessProgram = !appData.zippyPlay;
1029 appData.zippyPlay = FALSE;
1030 appData.zippyTalk = FALSE;
1031 appData.noChessProgram = TRUE;
1033 if (*appData.icsHelper != NULLCHAR) {
1034 appData.useTelnet = TRUE;
1035 appData.telnetProgram = appData.icsHelper;
1038 appData.zippyTalk = appData.zippyPlay = FALSE;
1041 /* [AS] Initialize pv info list [HGM] and game state */
1045 for( i=0; i<=framePtr; i++ ) {
1046 pvInfoList[i].depth = -1;
1047 boards[i][EP_STATUS] = EP_NONE;
1048 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1054 /* [AS] Adjudication threshold */
1055 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057 InitEngine(&first, 0);
1058 InitEngine(&second, 1);
1061 pairing.which = "pairing"; // pairing engine
1062 pairing.pr = NoProc;
1064 pairing.program = appData.pairingEngine;
1065 pairing.host = "localhost";
1068 if (appData.icsActive) {
1069 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1070 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071 appData.clockMode = FALSE;
1072 first.sendTime = second.sendTime = 0;
1076 /* Override some settings from environment variables, for backward
1077 compatibility. Unfortunately it's not feasible to have the env
1078 vars just set defaults, at least in xboard. Ugh.
1080 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085 if (!appData.icsActive) {
1089 /* Check for variants that are supported only in ICS mode,
1090 or not at all. Some that are accepted here nevertheless
1091 have bugs; see comments below.
1093 VariantClass variant = StringToVariant(appData.variant);
1095 case VariantBughouse: /* need four players and two boards */
1096 case VariantKriegspiel: /* need to hide pieces and move details */
1097 /* case VariantFischeRandom: (Fabien: moved below) */
1098 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099 if( (len >= MSG_SIZ) && appData.debugMode )
1100 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102 DisplayFatalError(buf, 0, 2);
1105 case VariantUnknown:
1106 case VariantLoadable:
1116 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117 if( (len >= MSG_SIZ) && appData.debugMode )
1118 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120 DisplayFatalError(buf, 0, 2);
1123 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1124 case VariantFairy: /* [HGM] TestLegality definitely off! */
1125 case VariantGothic: /* [HGM] should work */
1126 case VariantCapablanca: /* [HGM] should work */
1127 case VariantCourier: /* [HGM] initial forced moves not implemented */
1128 case VariantShogi: /* [HGM] could still mate with pawn drop */
1129 case VariantKnightmate: /* [HGM] should work */
1130 case VariantCylinder: /* [HGM] untested */
1131 case VariantFalcon: /* [HGM] untested */
1132 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133 offboard interposition not understood */
1134 case VariantNormal: /* definitely works! */
1135 case VariantWildCastle: /* pieces not automatically shuffled */
1136 case VariantNoCastle: /* pieces not automatically shuffled */
1137 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138 case VariantLosers: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantSuicide: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantGiveaway: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantTwoKings: /* should work */
1145 case VariantAtomic: /* should work except for win condition */
1146 case Variant3Check: /* should work except for win condition */
1147 case VariantShatranj: /* should work except for all win conditions */
1148 case VariantMakruk: /* should work except for draw countdown */
1149 case VariantBerolina: /* might work if TestLegality is off */
1150 case VariantCapaRandom: /* should work */
1151 case VariantJanus: /* should work */
1152 case VariantSuper: /* experimental */
1153 case VariantGreat: /* experimental, requires legality testing to be off */
1154 case VariantSChess: /* S-Chess, should work */
1155 case VariantGrand: /* should work */
1156 case VariantSpartan: /* should work */
1164 NextIntegerFromString (char ** str, long * value)
1169 while( *s == ' ' || *s == '\t' ) {
1175 if( *s >= '0' && *s <= '9' ) {
1176 while( *s >= '0' && *s <= '9' ) {
1177 *value = *value * 10 + (*s - '0');
1190 NextTimeControlFromString (char ** str, long * value)
1193 int result = NextIntegerFromString( str, &temp );
1196 *value = temp * 60; /* Minutes */
1197 if( **str == ':' ) {
1199 result = NextIntegerFromString( str, &temp );
1200 *value += temp; /* Seconds */
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210 int result = -1, type = 0; long temp, temp2;
1212 if(**str != ':') return -1; // old params remain in force!
1214 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215 if( NextIntegerFromString( str, &temp ) ) return -1;
1216 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219 /* time only: incremental or sudden-death time control */
1220 if(**str == '+') { /* increment follows; read it */
1222 if(**str == '!') type = *(*str)++; // Bronstein TC
1223 if(result = NextIntegerFromString( str, &temp2)) return -1;
1224 *inc = temp2 * 1000;
1225 if(**str == '.') { // read fraction of increment
1226 char *start = ++(*str);
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229 while(start++ < *str) temp2 /= 10;
1233 *moves = 0; *tc = temp * 1000; *incType = type;
1237 (*str)++; /* classical time control */
1238 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 { /* [HGM] get time to add from the multi-session time-control string */
1252 int incType, moves=1; /* kludge to force reading of first session */
1253 long time, increment;
1256 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260 if(movenr == -1) return time; /* last move before new session */
1261 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263 if(!moves) return increment; /* current session is incremental */
1264 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265 } while(movenr >= -1); /* try again for next session */
1267 return 0; // no new time quota on this move
1271 ParseTimeControl (char *tc, float ti, int mps)
1275 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1284 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291 snprintf(buf, MSG_SIZ, ":%s", mytc);
1293 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300 /* Parse second time control */
1303 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1311 timeControl_2 = tc2 * 1000;
1321 timeControl = tc1 * 1000;
1324 timeIncrement = ti * 1000; /* convert to ms */
1325 movesPerSession = 0;
1328 movesPerSession = mps;
1336 if (appData.debugMode) {
1337 fprintf(debugFP, "%s\n", programVersion);
1339 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341 set_cont_sequence(appData.wrapContSeq);
1342 if (appData.matchGames > 0) {
1343 appData.matchMode = TRUE;
1344 } else if (appData.matchMode) {
1345 appData.matchGames = 1;
1347 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348 appData.matchGames = appData.sameColorGames;
1349 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354 if (appData.noChessProgram || first.protocolVersion == 1) {
1357 /* kludge: allow timeout for initial "feature" commands */
1359 DisplayMessage("", _("Starting chess program"));
1360 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365 CalculateIndex (int index, int gameNr)
1366 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368 if(index > 0) return index; // fixed nmber
1369 if(index == 0) return 1;
1370 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376 LoadGameOrPosition (int gameNr)
1377 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378 if (*appData.loadGameFile != NULLCHAR) {
1379 if (!LoadGameFromFile(appData.loadGameFile,
1380 CalculateIndex(appData.loadGameIndex, gameNr),
1381 appData.loadGameFile, FALSE)) {
1382 DisplayFatalError(_("Bad game file"), 0, 1);
1385 } else if (*appData.loadPositionFile != NULLCHAR) {
1386 if (!LoadPositionFromFile(appData.loadPositionFile,
1387 CalculateIndex(appData.loadPositionIndex, gameNr),
1388 appData.loadPositionFile)) {
1389 DisplayFatalError(_("Bad position file"), 0, 1);
1397 ReserveGame (int gameNr, char resChar)
1399 FILE *tf = fopen(appData.tourneyFile, "r+");
1400 char *p, *q, c, buf[MSG_SIZ];
1401 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402 safeStrCpy(buf, lastMsg, MSG_SIZ);
1403 DisplayMessage(_("Pick new game"), "");
1404 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405 ParseArgsFromFile(tf);
1406 p = q = appData.results;
1407 if(appData.debugMode) {
1408 char *r = appData.participants;
1409 fprintf(debugFP, "results = '%s'\n", p);
1410 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411 fprintf(debugFP, "\n");
1413 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416 safeStrCpy(q, p, strlen(p) + 2);
1417 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423 fseek(tf, -(strlen(p)+4), SEEK_END);
1425 if(c != '"') // depending on DOS or Unix line endings we can be one off
1426 fseek(tf, -(strlen(p)+2), SEEK_END);
1427 else fseek(tf, -(strlen(p)+3), SEEK_END);
1428 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429 DisplayMessage(buf, "");
1430 free(p); appData.results = q;
1431 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433 int round = appData.defaultMatchGames * appData.tourneyType;
1434 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1435 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436 UnloadEngine(&first); // next game belongs to other pairing;
1437 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1443 MatchEvent (int mode)
1444 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446 if(matchMode) { // already in match mode: switch it off
1448 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451 // if(gameMode != BeginningOfGame) {
1452 // DisplayError(_("You can only start a match from the initial position."), 0);
1456 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457 /* Set up machine vs. machine match */
1459 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460 if(appData.tourneyFile[0]) {
1462 if(nextGame > appData.matchGames) {
1464 if(strchr(appData.results, '*') == NULL) {
1466 appData.tourneyCycles++;
1467 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469 NextTourneyGame(-1, &dummy);
1471 if(nextGame <= appData.matchGames) {
1472 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474 ScheduleDelayedEvent(NextMatchGame, 10000);
1479 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480 DisplayError(buf, 0);
1481 appData.tourneyFile[0] = 0;
1485 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1486 DisplayFatalError(_("Can't have a match with no chess programs"),
1491 matchGame = roundNr = 1;
1492 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499 InitBackEnd3 P((void))
1501 GameMode initialMode;
1505 InitChessProgram(&first, startedFromSetupPosition);
1507 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1508 free(programVersion);
1509 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514 if (appData.icsActive) {
1516 /* [DM] Make a console window if needed [HGM] merged ifs */
1522 if (*appData.icsCommPort != NULLCHAR)
1523 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524 appData.icsCommPort);
1526 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527 appData.icsHost, appData.icsPort);
1529 if( (len >= MSG_SIZ) && appData.debugMode )
1530 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532 DisplayFatalError(buf, err, 1);
1537 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542 } else if (appData.noChessProgram) {
1548 if (*appData.cmailGameName != NULLCHAR) {
1550 OpenLoopback(&cmailPR);
1552 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1556 DisplayMessage("", "");
1557 if (StrCaseCmp(appData.initialMode, "") == 0) {
1558 initialMode = BeginningOfGame;
1559 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566 initialMode = TwoMachinesPlay;
1567 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568 initialMode = AnalyzeFile;
1569 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570 initialMode = AnalyzeMode;
1571 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572 initialMode = MachinePlaysWhite;
1573 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574 initialMode = MachinePlaysBlack;
1575 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576 initialMode = EditGame;
1577 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578 initialMode = EditPosition;
1579 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580 initialMode = Training;
1582 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, 0, 2);
1590 if (appData.matchMode) {
1591 if(appData.tourneyFile[0]) { // start tourney from command line
1593 if(f = fopen(appData.tourneyFile, "r")) {
1594 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596 appData.clockMode = TRUE;
1598 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601 } else if (*appData.cmailGameName != NULLCHAR) {
1602 /* Set up cmail mode */
1603 ReloadCmailMsgEvent(TRUE);
1605 /* Set up other modes */
1606 if (initialMode == AnalyzeFile) {
1607 if (*appData.loadGameFile == NULLCHAR) {
1608 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1612 if (*appData.loadGameFile != NULLCHAR) {
1613 (void) LoadGameFromFile(appData.loadGameFile,
1614 appData.loadGameIndex,
1615 appData.loadGameFile, TRUE);
1616 } else if (*appData.loadPositionFile != NULLCHAR) {
1617 (void) LoadPositionFromFile(appData.loadPositionFile,
1618 appData.loadPositionIndex,
1619 appData.loadPositionFile);
1620 /* [HGM] try to make self-starting even after FEN load */
1621 /* to allow automatic setup of fairy variants with wtm */
1622 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623 gameMode = BeginningOfGame;
1624 setboardSpoiledMachineBlack = 1;
1626 /* [HGM] loadPos: make that every new game uses the setup */
1627 /* from file as long as we do not switch variant */
1628 if(!blackPlaysFirst) {
1629 startedFromPositionFile = TRUE;
1630 CopyBoard(filePosition, boards[0]);
1633 if (initialMode == AnalyzeMode) {
1634 if (appData.noChessProgram) {
1635 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638 if (appData.icsActive) {
1639 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1643 } else if (initialMode == AnalyzeFile) {
1644 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645 ShowThinkingEvent();
1647 AnalysisPeriodicEvent(1);
1648 } else if (initialMode == MachinePlaysWhite) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1654 if (appData.icsActive) {
1655 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1659 MachineWhiteEvent();
1660 } else if (initialMode == MachinePlaysBlack) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1666 if (appData.icsActive) {
1667 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1671 MachineBlackEvent();
1672 } else if (initialMode == TwoMachinesPlay) {
1673 if (appData.noChessProgram) {
1674 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1678 if (appData.icsActive) {
1679 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684 } else if (initialMode == EditGame) {
1686 } else if (initialMode == EditPosition) {
1687 EditPositionEvent();
1688 } else if (initialMode == Training) {
1689 if (*appData.loadGameFile == NULLCHAR) {
1690 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 DisplayBook(current+1);
1703 MoveHistorySet( movelist, first, last, current, pvInfoList );
1705 EvalGraphSet( first, last, current, pvInfoList );
1707 MakeEngineOutputTitle();
1711 * Establish will establish a contact to a remote host.port.
1712 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713 * used to talk to the host.
1714 * Returns 0 if okay, error code if not.
1721 if (*appData.icsCommPort != NULLCHAR) {
1722 /* Talk to the host through a serial comm port */
1723 return OpenCommPort(appData.icsCommPort, &icsPR);
1725 } else if (*appData.gateway != NULLCHAR) {
1726 if (*appData.remoteShell == NULLCHAR) {
1727 /* Use the rcmd protocol to run telnet program on a gateway host */
1728 snprintf(buf, sizeof(buf), "%s %s %s",
1729 appData.telnetProgram, appData.icsHost, appData.icsPort);
1730 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733 /* Use the rsh program to run telnet program on a gateway host */
1734 if (*appData.remoteUser == NULLCHAR) {
1735 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736 appData.gateway, appData.telnetProgram,
1737 appData.icsHost, appData.icsPort);
1739 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740 appData.remoteShell, appData.gateway,
1741 appData.remoteUser, appData.telnetProgram,
1742 appData.icsHost, appData.icsPort);
1744 return StartChildProcess(buf, "", &icsPR);
1747 } else if (appData.useTelnet) {
1748 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751 /* TCP socket interface differs somewhat between
1752 Unix and NT; handle details in the front end.
1754 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759 EscapeExpand (char *p, char *q)
1760 { // [HGM] initstring: routine to shape up string arguments
1761 while(*p++ = *q++) if(p[-1] == '\\')
1763 case 'n': p[-1] = '\n'; break;
1764 case 'r': p[-1] = '\r'; break;
1765 case 't': p[-1] = '\t'; break;
1766 case '\\': p[-1] = '\\'; break;
1767 case 0: *p = 0; return;
1768 default: p[-1] = q[-1]; break;
1773 show_bytes (FILE *fp, char *buf, int count)
1776 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777 fprintf(fp, "\\%03o", *buf & 0xff);
1786 /* Returns an errno value */
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 char buf[8192], *p, *q, *buflim;
1791 int left, newcount, outcount;
1793 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794 *appData.gateway != NULLCHAR) {
1795 if (appData.debugMode) {
1796 fprintf(debugFP, ">ICS: ");
1797 show_bytes(debugFP, message, count);
1798 fprintf(debugFP, "\n");
1800 return OutputToProcess(pr, message, count, outError);
1803 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, buf, newcount);
1813 fprintf(debugFP, "\n");
1815 outcount = OutputToProcess(pr, buf, newcount, outError);
1816 if (outcount < newcount) return -1; /* to be sure */
1823 } else if (((unsigned char) *p) == TN_IAC) {
1824 *q++ = (char) TN_IAC;
1831 if (appData.debugMode) {
1832 fprintf(debugFP, ">ICS: ");
1833 show_bytes(debugFP, buf, newcount);
1834 fprintf(debugFP, "\n");
1836 outcount = OutputToProcess(pr, buf, newcount, outError);
1837 if (outcount < newcount) return -1; /* to be sure */
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 int outError, outCount;
1845 static int gotEof = 0;
1847 /* Pass data read from player on to ICS */
1850 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851 if (outCount < count) {
1852 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854 } else if (count < 0) {
1855 RemoveInputSource(isr);
1856 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857 } else if (gotEof++ > 0) {
1858 RemoveInputSource(isr);
1859 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1865 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868 SendToICS("date\n");
1869 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1872 /* added routine for printf style output to ics */
1874 ics_printf (char *format, ...)
1876 char buffer[MSG_SIZ];
1879 va_start(args, format);
1880 vsnprintf(buffer, sizeof(buffer), format, args);
1881 buffer[sizeof(buffer)-1] = '\0';
1889 int count, outCount, outError;
1891 if (icsPR == NoProc) return;
1894 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895 if (outCount < count) {
1896 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 /* This is used for sending logon scripts to the ICS. Sending
1901 without a delay causes problems when using timestamp on ICC
1902 (at least on my machine). */
1904 SendToICSDelayed (char *s, long msdelay)
1906 int count, outCount, outError;
1908 if (icsPR == NoProc) return;
1911 if (appData.debugMode) {
1912 fprintf(debugFP, ">ICS: ");
1913 show_bytes(debugFP, s, count);
1914 fprintf(debugFP, "\n");
1916 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918 if (outCount < count) {
1919 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1924 /* Remove all highlighting escape sequences in s
1925 Also deletes any suffix starting with '('
1928 StripHighlightAndTitle (char *s)
1930 static char retbuf[MSG_SIZ];
1933 while (*s != NULLCHAR) {
1934 while (*s == '\033') {
1935 while (*s != NULLCHAR && !isalpha(*s)) s++;
1936 if (*s != NULLCHAR) s++;
1938 while (*s != NULLCHAR && *s != '\033') {
1939 if (*s == '(' || *s == '[') {
1950 /* Remove all highlighting escape sequences in s */
1952 StripHighlight (char *s)
1954 static char retbuf[MSG_SIZ];
1957 while (*s != NULLCHAR) {
1958 while (*s == '\033') {
1959 while (*s != NULLCHAR && !isalpha(*s)) s++;
1960 if (*s != NULLCHAR) s++;
1962 while (*s != NULLCHAR && *s != '\033') {
1970 char *variantNames[] = VARIANT_NAMES;
1972 VariantName (VariantClass v)
1974 return variantNames[v];
1978 /* Identify a variant from the strings the chess servers use or the
1979 PGN Variant tag names we use. */
1981 StringToVariant (char *e)
1985 VariantClass v = VariantNormal;
1986 int i, found = FALSE;
1992 /* [HGM] skip over optional board-size prefixes */
1993 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995 while( *e++ != '_');
1998 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2002 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003 if (StrCaseStr(e, variantNames[i])) {
2004 v = (VariantClass) i;
2011 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012 || StrCaseStr(e, "wild/fr")
2013 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014 v = VariantFischeRandom;
2015 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016 (i = 1, p = StrCaseStr(e, "w"))) {
2018 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2025 case 0: /* FICS only, actually */
2027 /* Castling legal even if K starts on d-file */
2028 v = VariantWildCastle;
2033 /* Castling illegal even if K & R happen to start in
2034 normal positions. */
2035 v = VariantNoCastle;
2048 /* Castling legal iff K & R start in normal positions */
2054 /* Special wilds for position setup; unclear what to do here */
2055 v = VariantLoadable;
2058 /* Bizarre ICC game */
2059 v = VariantTwoKings;
2062 v = VariantKriegspiel;
2068 v = VariantFischeRandom;
2071 v = VariantCrazyhouse;
2074 v = VariantBughouse;
2080 /* Not quite the same as FICS suicide! */
2081 v = VariantGiveaway;
2087 v = VariantShatranj;
2090 /* Temporary names for future ICC types. The name *will* change in
2091 the next xboard/WinBoard release after ICC defines it. */
2129 v = VariantCapablanca;
2132 v = VariantKnightmate;
2138 v = VariantCylinder;
2144 v = VariantCapaRandom;
2147 v = VariantBerolina;
2159 /* Found "wild" or "w" in the string but no number;
2160 must assume it's normal chess. */
2164 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165 if( (len >= MSG_SIZ) && appData.debugMode )
2166 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168 DisplayError(buf, 0);
2174 if (appData.debugMode) {
2175 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176 e, wnum, VariantName(v));
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185 advance *index beyond it, and set leftover_start to the new value of
2186 *index; else return FALSE. If pattern contains the character '*', it
2187 matches any sequence of characters not containing '\r', '\n', or the
2188 character following the '*' (if any), and the matched sequence(s) are
2189 copied into star_match.
2192 looking_at ( char *buf, int *index, char *pattern)
2194 char *bufp = &buf[*index], *patternp = pattern;
2196 char *matchp = star_match[0];
2199 if (*patternp == NULLCHAR) {
2200 *index = leftover_start = bufp - buf;
2204 if (*bufp == NULLCHAR) return FALSE;
2205 if (*patternp == '*') {
2206 if (*bufp == *(patternp + 1)) {
2208 matchp = star_match[++star_count];
2212 } else if (*bufp == '\n' || *bufp == '\r') {
2214 if (*patternp == NULLCHAR)
2219 *matchp++ = *bufp++;
2223 if (*patternp != *bufp) return FALSE;
2230 SendToPlayer (char *data, int length)
2232 int error, outCount;
2233 outCount = OutputToProcess(NoProc, data, length, &error);
2234 if (outCount < length) {
2235 DisplayFatalError(_("Error writing to display"), error, 1);
2240 PackHolding (char packed[], char *holding)
2250 switch (runlength) {
2261 sprintf(q, "%d", runlength);
2273 /* Telnet protocol requests from the front end */
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2277 unsigned char msg[3];
2278 int outCount, outError;
2280 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282 if (appData.debugMode) {
2283 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2299 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2308 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2311 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2316 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2325 if (!appData.icsActive) return;
2326 TelnetRequest(TN_DO, TN_ECHO);
2332 if (!appData.icsActive) return;
2333 TelnetRequest(TN_DONT, TN_ECHO);
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 /* put the holdings sent to us by the server on the board holdings area */
2340 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2344 if(gameInfo.holdingsWidth < 2) return;
2345 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346 return; // prevent overwriting by pre-board holdings
2348 if( (int)lowestPiece >= BlackPawn ) {
2351 holdingsStartRow = BOARD_HEIGHT-1;
2354 holdingsColumn = BOARD_WIDTH-1;
2355 countsColumn = BOARD_WIDTH-2;
2356 holdingsStartRow = 0;
2360 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361 board[i][holdingsColumn] = EmptySquare;
2362 board[i][countsColumn] = (ChessSquare) 0;
2364 while( (p=*holdings++) != NULLCHAR ) {
2365 piece = CharToPiece( ToUpper(p) );
2366 if(piece == EmptySquare) continue;
2367 /*j = (int) piece - (int) WhitePawn;*/
2368 j = PieceToNumber(piece);
2369 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370 if(j < 0) continue; /* should not happen */
2371 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373 board[holdingsStartRow+j*direction][countsColumn]++;
2379 VariantSwitch (Board board, VariantClass newVariant)
2381 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382 static Board oldBoard;
2384 startedFromPositionFile = FALSE;
2385 if(gameInfo.variant == newVariant) return;
2387 /* [HGM] This routine is called each time an assignment is made to
2388 * gameInfo.variant during a game, to make sure the board sizes
2389 * are set to match the new variant. If that means adding or deleting
2390 * holdings, we shift the playing board accordingly
2391 * This kludge is needed because in ICS observe mode, we get boards
2392 * of an ongoing game without knowing the variant, and learn about the
2393 * latter only later. This can be because of the move list we requested,
2394 * in which case the game history is refilled from the beginning anyway,
2395 * but also when receiving holdings of a crazyhouse game. In the latter
2396 * case we want to add those holdings to the already received position.
2400 if (appData.debugMode) {
2401 fprintf(debugFP, "Switch board from %s to %s\n",
2402 VariantName(gameInfo.variant), VariantName(newVariant));
2403 setbuf(debugFP, NULL);
2405 shuffleOpenings = 0; /* [HGM] shuffle */
2406 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2410 newWidth = 9; newHeight = 9;
2411 gameInfo.holdingsSize = 7;
2412 case VariantBughouse:
2413 case VariantCrazyhouse:
2414 newHoldingsWidth = 2; break;
2418 newHoldingsWidth = 2;
2419 gameInfo.holdingsSize = 8;
2422 case VariantCapablanca:
2423 case VariantCapaRandom:
2426 newHoldingsWidth = gameInfo.holdingsSize = 0;
2429 if(newWidth != gameInfo.boardWidth ||
2430 newHeight != gameInfo.boardHeight ||
2431 newHoldingsWidth != gameInfo.holdingsWidth ) {
2433 /* shift position to new playing area, if needed */
2434 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435 for(i=0; i<BOARD_HEIGHT; i++)
2436 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439 for(i=0; i<newHeight; i++) {
2440 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444 for(i=0; i<BOARD_HEIGHT; i++)
2445 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2449 board[HOLDINGS_SET] = 0;
2450 gameInfo.boardWidth = newWidth;
2451 gameInfo.boardHeight = newHeight;
2452 gameInfo.holdingsWidth = newHoldingsWidth;
2453 gameInfo.variant = newVariant;
2454 InitDrawingSizes(-2, 0);
2455 } else gameInfo.variant = newVariant;
2456 CopyBoard(oldBoard, board); // remember correctly formatted board
2457 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2458 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2461 static int loggedOn = FALSE;
2463 /*-- Game start info cache: --*/
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\ ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2493 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495 if(r < minRating+100 && r >=0 ) r = minRating+100;
2496 if(r > maxRating) r = maxRating;
2497 if(tc < 1.) tc = 1.;
2498 if(tc > 95.) tc = 95.;
2499 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500 y = ((double)r - minRating)/(maxRating - minRating)
2501 * (h-vMargin-squareSize/8-1) + vMargin;
2502 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503 if(strstr(seekAdList[i], " u ")) color = 1;
2504 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505 !strstr(seekAdList[i], "bullet") &&
2506 !strstr(seekAdList[i], "blitz") &&
2507 !strstr(seekAdList[i], "standard") ) color = 2;
2508 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2513 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2515 char buf[MSG_SIZ], *ext = "";
2516 VariantClass v = StringToVariant(type);
2517 if(strstr(type, "wild")) {
2518 ext = type + 4; // append wild number
2519 if(v == VariantFischeRandom) type = "chess960"; else
2520 if(v == VariantLoadable) type = "setup"; else
2521 type = VariantName(v);
2523 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529 seekNrList[nrOfSeekAds] = nr;
2530 zList[nrOfSeekAds] = 0;
2531 seekAdList[nrOfSeekAds++] = StrSave(buf);
2532 if(plot) PlotSeekAd(nrOfSeekAds-1);
2537 EraseSeekDot (int i)
2539 int x = xList[i], y = yList[i], d=squareSize/4, k;
2540 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542 // now replot every dot that overlapped
2543 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544 int xx = xList[k], yy = yList[k];
2545 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546 DrawSeekDot(xx, yy, colorList[k]);
2551 RemoveSeekAd (int nr)
2554 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2556 if(seekAdList[i]) free(seekAdList[i]);
2557 seekAdList[i] = seekAdList[--nrOfSeekAds];
2558 seekNrList[i] = seekNrList[nrOfSeekAds];
2559 ratingList[i] = ratingList[nrOfSeekAds];
2560 colorList[i] = colorList[nrOfSeekAds];
2561 tcList[i] = tcList[nrOfSeekAds];
2562 xList[i] = xList[nrOfSeekAds];
2563 yList[i] = yList[nrOfSeekAds];
2564 zList[i] = zList[nrOfSeekAds];
2565 seekAdList[nrOfSeekAds] = NULL;
2571 MatchSoughtLine (char *line)
2573 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574 int nr, base, inc, u=0; char dummy;
2576 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2579 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2581 // match: compact and save the line
2582 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2592 if(!seekGraphUp) return FALSE;
2593 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2596 DrawSeekBackground(0, 0, w, h);
2597 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2602 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2605 snprintf(buf, MSG_SIZ, "%d", i);
2606 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2609 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610 for(i=1; i<100; i+=(i<10?1:5)) {
2611 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2615 snprintf(buf, MSG_SIZ, "%d", i);
2616 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2619 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2626 static int lastDown = 0, displayed = 0, lastSecond;
2627 if(y < 0) return FALSE;
2628 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630 if(!seekGraphUp) return FALSE;
2631 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632 DrawPosition(TRUE, NULL);
2635 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636 if(click == Release || moving) return FALSE;
2638 soughtPending = TRUE;
2639 SendToICS(ics_prefix);
2640 SendToICS("sought\n"); // should this be "sought all"?
2641 } else { // issue challenge based on clicked ad
2642 int dist = 10000; int i, closest = 0, second = 0;
2643 for(i=0; i<nrOfSeekAds; i++) {
2644 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2645 if(d < dist) { dist = d; closest = i; }
2646 second += (d - zList[i] < 120); // count in-range ads
2647 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2651 second = (second > 1);
2652 if(displayed != closest || second != lastSecond) {
2653 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654 lastSecond = second; displayed = closest;
2656 if(click == Press) {
2657 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2660 } // on press 'hit', only show info
2661 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663 SendToICS(ics_prefix);
2665 return TRUE; // let incoming board of started game pop down the graph
2666 } else if(click == Release) { // release 'miss' is ignored
2667 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668 if(moving == 2) { // right up-click
2669 nrOfSeekAds = 0; // refresh graph
2670 soughtPending = TRUE;
2671 SendToICS(ics_prefix);
2672 SendToICS("sought\n"); // should this be "sought all"?
2675 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676 // press miss or release hit 'pop down' seek graph
2677 seekGraphUp = FALSE;
2678 DrawPosition(TRUE, NULL);
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2696 static int started = STARTED_NONE;
2697 static char parse[20000];
2698 static int parse_pos = 0;
2699 static char buf[BUF_SIZE + 1];
2700 static int firstTime = TRUE, intfSet = FALSE;
2701 static ColorClass prevColor = ColorNormal;
2702 static int savingComment = FALSE;
2703 static int cmatch = 0; // continuation sequence match
2710 int backup; /* [DM] For zippy color lines */
2712 char talker[MSG_SIZ]; // [HGM] chat
2715 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2717 if (appData.debugMode) {
2719 fprintf(debugFP, "<ICS: ");
2720 show_bytes(debugFP, data, count);
2721 fprintf(debugFP, "\n");
2725 if (appData.debugMode) { int f = forwardMostMove;
2726 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2731 /* If last read ended with a partial line that we couldn't parse,
2732 prepend it to the new read and try again. */
2733 if (leftover_len > 0) {
2734 for (i=0; i<leftover_len; i++)
2735 buf[i] = buf[leftover_start + i];
2738 /* copy new characters into the buffer */
2739 bp = buf + leftover_len;
2740 buf_len=leftover_len;
2741 for (i=0; i<count; i++)
2744 if (data[i] == '\r')
2747 // join lines split by ICS?
2748 if (!appData.noJoin)
2751 Joining just consists of finding matches against the
2752 continuation sequence, and discarding that sequence
2753 if found instead of copying it. So, until a match
2754 fails, there's nothing to do since it might be the
2755 complete sequence, and thus, something we don't want
2758 if (data[i] == cont_seq[cmatch])
2761 if (cmatch == strlen(cont_seq))
2763 cmatch = 0; // complete match. just reset the counter
2766 it's possible for the ICS to not include the space
2767 at the end of the last word, making our [correct]
2768 join operation fuse two separate words. the server
2769 does this when the space occurs at the width setting.
2771 if (!buf_len || buf[buf_len-1] != ' ')
2782 match failed, so we have to copy what matched before
2783 falling through and copying this character. In reality,
2784 this will only ever be just the newline character, but
2785 it doesn't hurt to be precise.
2787 strncpy(bp, cont_seq, cmatch);
2799 buf[buf_len] = NULLCHAR;
2800 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2805 while (i < buf_len) {
2806 /* Deal with part of the TELNET option negotiation
2807 protocol. We refuse to do anything beyond the
2808 defaults, except that we allow the WILL ECHO option,
2809 which ICS uses to turn off password echoing when we are
2810 directly connected to it. We reject this option
2811 if localLineEditing mode is on (always on in xboard)
2812 and we are talking to port 23, which might be a real
2813 telnet server that will try to keep WILL ECHO on permanently.
2815 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817 unsigned char option;
2819 switch ((unsigned char) buf[++i]) {
2821 if (appData.debugMode)
2822 fprintf(debugFP, "\n<WILL ");
2823 switch (option = (unsigned char) buf[++i]) {
2825 if (appData.debugMode)
2826 fprintf(debugFP, "ECHO ");
2827 /* Reply only if this is a change, according
2828 to the protocol rules. */
2829 if (remoteEchoOption) break;
2830 if (appData.localLineEditing &&
2831 atoi(appData.icsPort) == TN_PORT) {
2832 TelnetRequest(TN_DONT, TN_ECHO);
2835 TelnetRequest(TN_DO, TN_ECHO);
2836 remoteEchoOption = TRUE;
2840 if (appData.debugMode)
2841 fprintf(debugFP, "%d ", option);
2842 /* Whatever this is, we don't want it. */
2843 TelnetRequest(TN_DONT, option);
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<WONT ");
2850 switch (option = (unsigned char) buf[++i]) {
2852 if (appData.debugMode)
2853 fprintf(debugFP, "ECHO ");
2854 /* Reply only if this is a change, according
2855 to the protocol rules. */
2856 if (!remoteEchoOption) break;
2858 TelnetRequest(TN_DONT, TN_ECHO);
2859 remoteEchoOption = FALSE;
2862 if (appData.debugMode)
2863 fprintf(debugFP, "%d ", (unsigned char) option);
2864 /* Whatever this is, it must already be turned
2865 off, because we never agree to turn on
2866 anything non-default, so according to the
2867 protocol rules, we don't reply. */
2872 if (appData.debugMode)
2873 fprintf(debugFP, "\n<DO ");
2874 switch (option = (unsigned char) buf[++i]) {
2876 /* Whatever this is, we refuse to do it. */
2877 if (appData.debugMode)
2878 fprintf(debugFP, "%d ", option);
2879 TelnetRequest(TN_WONT, option);
2884 if (appData.debugMode)
2885 fprintf(debugFP, "\n<DONT ");
2886 switch (option = (unsigned char) buf[++i]) {
2888 if (appData.debugMode)
2889 fprintf(debugFP, "%d ", option);
2890 /* Whatever this is, we are already not doing
2891 it, because we never agree to do anything
2892 non-default, so according to the protocol
2893 rules, we don't reply. */
2898 if (appData.debugMode)
2899 fprintf(debugFP, "\n<IAC ");
2900 /* Doubled IAC; pass it through */
2904 if (appData.debugMode)
2905 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906 /* Drop all other telnet commands on the floor */
2909 if (oldi > next_out)
2910 SendToPlayer(&buf[next_out], oldi - next_out);
2916 /* OK, this at least will *usually* work */
2917 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2921 if (loggedOn && !intfSet) {
2922 if (ics_type == ICS_ICC) {
2923 snprintf(str, MSG_SIZ,
2924 "/set-quietly interface %s\n/set-quietly style 12\n",
2926 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927 strcat(str, "/set-2 51 1\n/set seek 1\n");
2928 } else if (ics_type == ICS_CHESSNET) {
2929 snprintf(str, MSG_SIZ, "/style 12\n");
2931 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932 strcat(str, programVersion);
2933 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2937 strcat(str, "$iset nohighlight 1\n");
2939 strcat(str, "$iset lock 1\n$style 12\n");
2942 NotifyFrontendLogin();
2946 if (started == STARTED_COMMENT) {
2947 /* Accumulate characters in comment */
2948 parse[parse_pos++] = buf[i];
2949 if (buf[i] == '\n') {
2950 parse[parse_pos] = NULLCHAR;
2951 if(chattingPartner>=0) {
2953 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954 OutputChatMessage(chattingPartner, mess);
2955 chattingPartner = -1;
2956 next_out = i+1; // [HGM] suppress printing in ICS window
2958 if(!suppressKibitz) // [HGM] kibitz
2959 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961 int nrDigit = 0, nrAlph = 0, j;
2962 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964 parse[parse_pos] = NULLCHAR;
2965 // try to be smart: if it does not look like search info, it should go to
2966 // ICS interaction window after all, not to engine-output window.
2967 for(j=0; j<parse_pos; j++) { // count letters and digits
2968 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2970 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2972 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973 int depth=0; float score;
2974 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976 pvInfoList[forwardMostMove-1].depth = depth;
2977 pvInfoList[forwardMostMove-1].score = 100*score;
2979 OutputKibitz(suppressKibitz, parse);
2982 if(gameMode == IcsObserving) // restore original ICS messages
2983 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2985 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986 SendToPlayer(tmp, strlen(tmp));
2988 next_out = i+1; // [HGM] suppress printing in ICS window
2990 started = STARTED_NONE;
2992 /* Don't match patterns against characters in comment */
2997 if (started == STARTED_CHATTER) {
2998 if (buf[i] != '\n') {
2999 /* Don't match patterns against characters in chatter */
3003 started = STARTED_NONE;
3004 if(suppressKibitz) next_out = i+1;
3007 /* Kludge to deal with rcmd protocol */
3008 if (firstTime && looking_at(buf, &i, "\001*")) {
3009 DisplayFatalError(&buf[1], 0, 1);
3015 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3018 if (appData.debugMode)
3019 fprintf(debugFP, "ics_type %d\n", ics_type);
3022 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023 ics_type = ICS_FICS;
3025 if (appData.debugMode)
3026 fprintf(debugFP, "ics_type %d\n", ics_type);
3029 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030 ics_type = ICS_CHESSNET;
3032 if (appData.debugMode)
3033 fprintf(debugFP, "ics_type %d\n", ics_type);
3038 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039 looking_at(buf, &i, "Logging you in as \"*\"") ||
3040 looking_at(buf, &i, "will be \"*\""))) {
3041 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3045 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3047 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048 DisplayIcsInteractionTitle(buf);
3049 have_set_title = TRUE;
3052 /* skip finger notes */
3053 if (started == STARTED_NONE &&
3054 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055 (buf[i] == '1' && buf[i+1] == '0')) &&
3056 buf[i+2] == ':' && buf[i+3] == ' ') {
3057 started = STARTED_CHATTER;
3063 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064 if(appData.seekGraph) {
3065 if(soughtPending && MatchSoughtLine(buf+i)) {
3066 i = strstr(buf+i, "rated") - buf;
3067 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068 next_out = leftover_start = i;
3069 started = STARTED_CHATTER;
3070 suppressKibitz = TRUE;
3073 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074 && looking_at(buf, &i, "* ads displayed")) {
3075 soughtPending = FALSE;
3080 if(appData.autoRefresh) {
3081 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082 int s = (ics_type == ICS_ICC); // ICC format differs
3084 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086 looking_at(buf, &i, "*% "); // eat prompt
3087 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089 next_out = i; // suppress
3092 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093 char *p = star_match[0];
3095 if(seekGraphUp) RemoveSeekAd(atoi(p));
3096 while(*p && *p++ != ' '); // next
3098 looking_at(buf, &i, "*% "); // eat prompt
3099 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106 /* skip formula vars */
3107 if (started == STARTED_NONE &&
3108 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109 started = STARTED_CHATTER;
3114 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115 if (appData.autoKibitz && started == STARTED_NONE &&
3116 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3117 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3122 suppressKibitz = TRUE;
3123 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126 && (gameMode == IcsPlayingWhite)) ||
3127 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3129 started = STARTED_CHATTER; // own kibitz we simply discard
3131 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132 parse_pos = 0; parse[0] = NULLCHAR;
3133 savingComment = TRUE;
3134 suppressKibitz = gameMode != IcsObserving ? 2 :
3135 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3139 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141 && atoi(star_match[0])) {
3142 // suppress the acknowledgements of our own autoKibitz
3144 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146 SendToPlayer(star_match[0], strlen(star_match[0]));
3147 if(looking_at(buf, &i, "*% ")) // eat prompt
3148 suppressKibitz = FALSE;
3152 } // [HGM] kibitz: end of patch
3154 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3156 // [HGM] chat: intercept tells by users for which we have an open chat window
3158 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159 looking_at(buf, &i, "* whispers:") ||
3160 looking_at(buf, &i, "* kibitzes:") ||
3161 looking_at(buf, &i, "* shouts:") ||
3162 looking_at(buf, &i, "* c-shouts:") ||
3163 looking_at(buf, &i, "--> * ") ||
3164 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3169 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170 chattingPartner = -1;
3172 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173 for(p=0; p<MAX_CHAT; p++) {
3174 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175 talker[0] = '['; strcat(talker, "] ");
3176 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177 chattingPartner = p; break;
3180 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181 for(p=0; p<MAX_CHAT; p++) {
3182 if(!strcmp("kibitzes", chatPartner[p])) {
3183 talker[0] = '['; strcat(talker, "] ");
3184 chattingPartner = p; break;
3187 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188 for(p=0; p<MAX_CHAT; p++) {
3189 if(!strcmp("whispers", chatPartner[p])) {
3190 talker[0] = '['; strcat(talker, "] ");
3191 chattingPartner = p; break;
3194 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195 if(buf[i-8] == '-' && buf[i-3] == 't')
3196 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197 if(!strcmp("c-shouts", chatPartner[p])) {
3198 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199 chattingPartner = p; break;
3202 if(chattingPartner < 0)
3203 for(p=0; p<MAX_CHAT; p++) {
3204 if(!strcmp("shouts", chatPartner[p])) {
3205 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208 chattingPartner = p; break;
3212 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214 talker[0] = 0; Colorize(ColorTell, FALSE);
3215 chattingPartner = p; break;
3217 if(chattingPartner<0) i = oldi; else {
3218 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221 started = STARTED_COMMENT;
3222 parse_pos = 0; parse[0] = NULLCHAR;
3223 savingComment = 3 + chattingPartner; // counts as TRUE
3224 suppressKibitz = TRUE;
3227 } // [HGM] chat: end of patch
3230 if (appData.zippyTalk || appData.zippyPlay) {
3231 /* [DM] Backup address for color zippy lines */
3233 if (loggedOn == TRUE)
3234 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3237 } // [DM] 'else { ' deleted
3239 /* Regular tells and says */
3240 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241 looking_at(buf, &i, "* (your partner) tells you: ") ||
3242 looking_at(buf, &i, "* says: ") ||
3243 /* Don't color "message" or "messages" output */
3244 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245 looking_at(buf, &i, "*. * at *:*: ") ||
3246 looking_at(buf, &i, "--* (*:*): ") ||
3247 /* Message notifications (same color as tells) */
3248 looking_at(buf, &i, "* has left a message ") ||
3249 looking_at(buf, &i, "* just sent you a message:\n") ||
3250 /* Whispers and kibitzes */
3251 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252 looking_at(buf, &i, "* kibitzes: ") ||
3254 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3256 if (tkind == 1 && strchr(star_match[0], ':')) {
3257 /* Avoid "tells you:" spoofs in channels */
3260 if (star_match[0][0] == NULLCHAR ||
3261 strchr(star_match[0], ' ') ||
3262 (tkind == 3 && strchr(star_match[1], ' '))) {
3263 /* Reject bogus matches */
3266 if (appData.colorize) {
3267 if (oldi > next_out) {
3268 SendToPlayer(&buf[next_out], oldi - next_out);
3273 Colorize(ColorTell, FALSE);
3274 curColor = ColorTell;
3277 Colorize(ColorKibitz, FALSE);
3278 curColor = ColorKibitz;
3281 p = strrchr(star_match[1], '(');
3288 Colorize(ColorChannel1, FALSE);
3289 curColor = ColorChannel1;
3291 Colorize(ColorChannel, FALSE);
3292 curColor = ColorChannel;
3296 curColor = ColorNormal;
3300 if (started == STARTED_NONE && appData.autoComment &&
3301 (gameMode == IcsObserving ||
3302 gameMode == IcsPlayingWhite ||
3303 gameMode == IcsPlayingBlack)) {
3304 parse_pos = i - oldi;
3305 memcpy(parse, &buf[oldi], parse_pos);
3306 parse[parse_pos] = NULLCHAR;
3307 started = STARTED_COMMENT;
3308 savingComment = TRUE;
3310 started = STARTED_CHATTER;
3311 savingComment = FALSE;
3318 if (looking_at(buf, &i, "* s-shouts: ") ||
3319 looking_at(buf, &i, "* c-shouts: ")) {
3320 if (appData.colorize) {
3321 if (oldi > next_out) {
3322 SendToPlayer(&buf[next_out], oldi - next_out);
3325 Colorize(ColorSShout, FALSE);
3326 curColor = ColorSShout;
3329 started = STARTED_CHATTER;
3333 if (looking_at(buf, &i, "--->")) {
3338 if (looking_at(buf, &i, "* shouts: ") ||
3339 looking_at(buf, &i, "--> ")) {
3340 if (appData.colorize) {
3341 if (oldi > next_out) {
3342 SendToPlayer(&buf[next_out], oldi - next_out);
3345 Colorize(ColorShout, FALSE);
3346 curColor = ColorShout;
3349 started = STARTED_CHATTER;
3353 if (looking_at( buf, &i, "Challenge:")) {
3354 if (appData.colorize) {
3355 if (oldi > next_out) {
3356 SendToPlayer(&buf[next_out], oldi - next_out);
3359 Colorize(ColorChallenge, FALSE);
3360 curColor = ColorChallenge;
3366 if (looking_at(buf, &i, "* offers you") ||
3367 looking_at(buf, &i, "* offers to be") ||
3368 looking_at(buf, &i, "* would like to") ||
3369 looking_at(buf, &i, "* requests to") ||
3370 looking_at(buf, &i, "Your opponent offers") ||
3371 looking_at(buf, &i, "Your opponent requests")) {
3373 if (appData.colorize) {
3374 if (oldi > next_out) {
3375 SendToPlayer(&buf[next_out], oldi - next_out);
3378 Colorize(ColorRequest, FALSE);
3379 curColor = ColorRequest;
3384 if (looking_at(buf, &i, "* (*) seeking")) {
3385 if (appData.colorize) {
3386 if (oldi > next_out) {
3387 SendToPlayer(&buf[next_out], oldi - next_out);
3390 Colorize(ColorSeek, FALSE);
3391 curColor = ColorSeek;
3396 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3398 if (looking_at(buf, &i, "\\ ")) {
3399 if (prevColor != ColorNormal) {
3400 if (oldi > next_out) {
3401 SendToPlayer(&buf[next_out], oldi - next_out);
3404 Colorize(prevColor, TRUE);
3405 curColor = prevColor;
3407 if (savingComment) {
3408 parse_pos = i - oldi;
3409 memcpy(parse, &buf[oldi], parse_pos);
3410 parse[parse_pos] = NULLCHAR;
3411 started = STARTED_COMMENT;
3412 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413 chattingPartner = savingComment - 3; // kludge to remember the box
3415 started = STARTED_CHATTER;
3420 if (looking_at(buf, &i, "Black Strength :") ||
3421 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422 looking_at(buf, &i, "<10>") ||
3423 looking_at(buf, &i, "#@#")) {
3424 /* Wrong board style */
3426 SendToICS(ics_prefix);
3427 SendToICS("set style 12\n");
3428 SendToICS(ics_prefix);
3429 SendToICS("refresh\n");
3433 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3435 have_sent_ICS_logon = 1;
3439 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440 (looking_at(buf, &i, "\n<12> ") ||
3441 looking_at(buf, &i, "<12> "))) {
3443 if (oldi > next_out) {
3444 SendToPlayer(&buf[next_out], oldi - next_out);
3447 started = STARTED_BOARD;
3452 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453 looking_at(buf, &i, "<b1> ")) {
3454 if (oldi > next_out) {
3455 SendToPlayer(&buf[next_out], oldi - next_out);
3458 started = STARTED_HOLDINGS;
3463 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3465 /* Header for a move list -- first line */
3467 switch (ics_getting_history) {
3471 case BeginningOfGame:
3472 /* User typed "moves" or "oldmoves" while we
3473 were idle. Pretend we asked for these
3474 moves and soak them up so user can step
3475 through them and/or save them.
3478 gameMode = IcsObserving;
3481 ics_getting_history = H_GOT_UNREQ_HEADER;
3483 case EditGame: /*?*/
3484 case EditPosition: /*?*/
3485 /* Should above feature work in these modes too? */
3486 /* For now it doesn't */
3487 ics_getting_history = H_GOT_UNWANTED_HEADER;
3490 ics_getting_history = H_GOT_UNWANTED_HEADER;
3495 /* Is this the right one? */
3496 if (gameInfo.white && gameInfo.black &&
3497 strcmp(gameInfo.white, star_match[0]) == 0 &&
3498 strcmp(gameInfo.black, star_match[2]) == 0) {
3500 ics_getting_history = H_GOT_REQ_HEADER;
3503 case H_GOT_REQ_HEADER:
3504 case H_GOT_UNREQ_HEADER:
3505 case H_GOT_UNWANTED_HEADER:
3506 case H_GETTING_MOVES:
3507 /* Should not happen */
3508 DisplayError(_("Error gathering move list: two headers"), 0);
3509 ics_getting_history = H_FALSE;
3513 /* Save player ratings into gameInfo if needed */
3514 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516 (gameInfo.whiteRating == -1 ||
3517 gameInfo.blackRating == -1)) {
3519 gameInfo.whiteRating = string_to_rating(star_match[1]);
3520 gameInfo.blackRating = string_to_rating(star_match[3]);
3521 if (appData.debugMode)
3522 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523 gameInfo.whiteRating, gameInfo.blackRating);
3528 if (looking_at(buf, &i,
3529 "* * match, initial time: * minute*, increment: * second")) {
3530 /* Header for a move list -- second line */
3531 /* Initial board will follow if this is a wild game */
3532 if (gameInfo.event != NULL) free(gameInfo.event);
3533 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534 gameInfo.event = StrSave(str);
3535 /* [HGM] we switched variant. Translate boards if needed. */
3536 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3540 if (looking_at(buf, &i, "Move ")) {
3541 /* Beginning of a move list */
3542 switch (ics_getting_history) {
3544 /* Normally should not happen */
3545 /* Maybe user hit reset while we were parsing */
3548 /* Happens if we are ignoring a move list that is not
3549 * the one we just requested. Common if the user
3550 * tries to observe two games without turning off
3553 case H_GETTING_MOVES:
3554 /* Should not happen */
3555 DisplayError(_("Error gathering move list: nested"), 0);
3556 ics_getting_history = H_FALSE;
3558 case H_GOT_REQ_HEADER:
3559 ics_getting_history = H_GETTING_MOVES;
3560 started = STARTED_MOVES;
3562 if (oldi > next_out) {
3563 SendToPlayer(&buf[next_out], oldi - next_out);
3566 case H_GOT_UNREQ_HEADER:
3567 ics_getting_history = H_GETTING_MOVES;
3568 started = STARTED_MOVES_NOHIDE;
3571 case H_GOT_UNWANTED_HEADER:
3572 ics_getting_history = H_FALSE;
3578 if (looking_at(buf, &i, "% ") ||
3579 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582 soughtPending = FALSE;
3586 if(suppressKibitz) next_out = i;
3587 savingComment = FALSE;
3591 case STARTED_MOVES_NOHIDE:
3592 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593 parse[parse_pos + i - oldi] = NULLCHAR;
3594 ParseGameHistory(parse);
3596 if (appData.zippyPlay && first.initDone) {
3597 FeedMovesToProgram(&first, forwardMostMove);
3598 if (gameMode == IcsPlayingWhite) {
3599 if (WhiteOnMove(forwardMostMove)) {
3600 if (first.sendTime) {
3601 if (first.useColors) {
3602 SendToProgram("black\n", &first);
3604 SendTimeRemaining(&first, TRUE);
3606 if (first.useColors) {
3607 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3609 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610 first.maybeThinking = TRUE;
3612 if (first.usePlayother) {
3613 if (first.sendTime) {
3614 SendTimeRemaining(&first, TRUE);
3616 SendToProgram("playother\n", &first);
3622 } else if (gameMode == IcsPlayingBlack) {
3623 if (!WhiteOnMove(forwardMostMove)) {
3624 if (first.sendTime) {
3625 if (first.useColors) {
3626 SendToProgram("white\n", &first);
3628 SendTimeRemaining(&first, FALSE);
3630 if (first.useColors) {
3631 SendToProgram("black\n", &first);
3633 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634 first.maybeThinking = TRUE;
3636 if (first.usePlayother) {
3637 if (first.sendTime) {
3638 SendTimeRemaining(&first, FALSE);
3640 SendToProgram("playother\n", &first);
3649 if (gameMode == IcsObserving && ics_gamenum == -1) {
3650 /* Moves came from oldmoves or moves command
3651 while we weren't doing anything else.
3653 currentMove = forwardMostMove;
3654 ClearHighlights();/*!!could figure this out*/
3655 flipView = appData.flipView;
3656 DrawPosition(TRUE, boards[currentMove]);
3657 DisplayBothClocks();
3658 snprintf(str, MSG_SIZ, "%s %s %s",
3659 gameInfo.white, _("vs."), gameInfo.black);
3663 /* Moves were history of an active game */
3664 if (gameInfo.resultDetails != NULL) {
3665 free(gameInfo.resultDetails);
3666 gameInfo.resultDetails = NULL;
3669 HistorySet(parseList, backwardMostMove,
3670 forwardMostMove, currentMove-1);
3671 DisplayMove(currentMove - 1);
3672 if (started == STARTED_MOVES) next_out = i;
3673 started = STARTED_NONE;
3674 ics_getting_history = H_FALSE;
3677 case STARTED_OBSERVE:
3678 started = STARTED_NONE;
3679 SendToICS(ics_prefix);
3680 SendToICS("refresh\n");
3686 if(bookHit) { // [HGM] book: simulate book reply
3687 static char bookMove[MSG_SIZ]; // a bit generous?
3689 programStats.nodes = programStats.depth = programStats.time =
3690 programStats.score = programStats.got_only_move = 0;
3691 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3693 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694 strcat(bookMove, bookHit);
3695 HandleMachineMove(bookMove, &first);
3700 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701 started == STARTED_HOLDINGS ||
3702 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703 /* Accumulate characters in move list or board */
3704 parse[parse_pos++] = buf[i];
3707 /* Start of game messages. Mostly we detect start of game
3708 when the first board image arrives. On some versions
3709 of the ICS, though, we need to do a "refresh" after starting
3710 to observe in order to get the current board right away. */
3711 if (looking_at(buf, &i, "Adding game * to observation list")) {
3712 started = STARTED_OBSERVE;
3716 /* Handle auto-observe */
3717 if (appData.autoObserve &&
3718 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3721 /* Choose the player that was highlighted, if any. */
3722 if (star_match[0][0] == '\033' ||
3723 star_match[1][0] != '\033') {
3724 player = star_match[0];
3726 player = star_match[2];
3728 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729 ics_prefix, StripHighlightAndTitle(player));
3732 /* Save ratings from notify string */
3733 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734 player1Rating = string_to_rating(star_match[1]);
3735 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736 player2Rating = string_to_rating(star_match[3]);
3738 if (appData.debugMode)
3740 "Ratings from 'Game notification:' %s %d, %s %d\n",
3741 player1Name, player1Rating,
3742 player2Name, player2Rating);
3747 /* Deal with automatic examine mode after a game,
3748 and with IcsObserving -> IcsExamining transition */
3749 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750 looking_at(buf, &i, "has made you an examiner of game *")) {
3752 int gamenum = atoi(star_match[0]);
3753 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754 gamenum == ics_gamenum) {
3755 /* We were already playing or observing this game;
3756 no need to refetch history */
3757 gameMode = IcsExamining;
3759 pauseExamForwardMostMove = forwardMostMove;
3760 } else if (currentMove < forwardMostMove) {
3761 ForwardInner(forwardMostMove);
3764 /* I don't think this case really can happen */
3765 SendToICS(ics_prefix);
3766 SendToICS("refresh\n");
3771 /* Error messages */
3772 // if (ics_user_moved) {
3773 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774 if (looking_at(buf, &i, "Illegal move") ||
3775 looking_at(buf, &i, "Not a legal move") ||
3776 looking_at(buf, &i, "Your king is in check") ||
3777 looking_at(buf, &i, "It isn't your turn") ||
3778 looking_at(buf, &i, "It is not your move")) {
3780 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781 currentMove = forwardMostMove-1;
3782 DisplayMove(currentMove - 1); /* before DMError */
3783 DrawPosition(FALSE, boards[currentMove]);
3784 SwitchClocks(forwardMostMove-1); // [HGM] race
3785 DisplayBothClocks();
3787 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3793 if (looking_at(buf, &i, "still have time") ||
3794 looking_at(buf, &i, "not out of time") ||
3795 looking_at(buf, &i, "either player is out of time") ||
3796 looking_at(buf, &i, "has timeseal; checking")) {
3797 /* We must have called his flag a little too soon */
3798 whiteFlag = blackFlag = FALSE;
3802 if (looking_at(buf, &i, "added * seconds to") ||
3803 looking_at(buf, &i, "seconds were added to")) {
3804 /* Update the clocks */
3805 SendToICS(ics_prefix);
3806 SendToICS("refresh\n");
3810 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811 ics_clock_paused = TRUE;
3816 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817 ics_clock_paused = FALSE;
3822 /* Grab player ratings from the Creating: message.
3823 Note we have to check for the special case when
3824 the ICS inserts things like [white] or [black]. */
3825 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3828 0 player 1 name (not necessarily white)
3830 2 empty, white, or black (IGNORED)
3831 3 player 2 name (not necessarily black)
3834 The names/ratings are sorted out when the game
3835 actually starts (below).
3837 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838 player1Rating = string_to_rating(star_match[1]);
3839 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840 player2Rating = string_to_rating(star_match[4]);
3842 if (appData.debugMode)
3844 "Ratings from 'Creating:' %s %d, %s %d\n",
3845 player1Name, player1Rating,
3846 player2Name, player2Rating);
3851 /* Improved generic start/end-of-game messages */
3852 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854 /* If tkind == 0: */
3855 /* star_match[0] is the game number */
3856 /* [1] is the white player's name */
3857 /* [2] is the black player's name */
3858 /* For end-of-game: */
3859 /* [3] is the reason for the game end */
3860 /* [4] is a PGN end game-token, preceded by " " */
3861 /* For start-of-game: */
3862 /* [3] begins with "Creating" or "Continuing" */
3863 /* [4] is " *" or empty (don't care). */
3864 int gamenum = atoi(star_match[0]);
3865 char *whitename, *blackname, *why, *endtoken;
3866 ChessMove endtype = EndOfFile;
3869 whitename = star_match[1];
3870 blackname = star_match[2];
3871 why = star_match[3];
3872 endtoken = star_match[4];
3874 whitename = star_match[1];
3875 blackname = star_match[3];
3876 why = star_match[5];
3877 endtoken = star_match[6];
3880 /* Game start messages */
3881 if (strncmp(why, "Creating ", 9) == 0 ||
3882 strncmp(why, "Continuing ", 11) == 0) {
3883 gs_gamenum = gamenum;
3884 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3888 if (appData.zippyPlay) {
3889 ZippyGameStart(whitename, blackname);
3892 partnerBoardValid = FALSE; // [HGM] bughouse
3896 /* Game end messages */
3897 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898 ics_gamenum != gamenum) {
3901 while (endtoken[0] == ' ') endtoken++;
3902 switch (endtoken[0]) {
3905 endtype = GameUnfinished;
3908 endtype = BlackWins;
3911 if (endtoken[1] == '/')
3912 endtype = GameIsDrawn;
3914 endtype = WhiteWins;
3917 GameEnds(endtype, why, GE_ICS);
3919 if (appData.zippyPlay && first.initDone) {
3920 ZippyGameEnd(endtype, why);
3921 if (first.pr == NoProc) {
3922 /* Start the next process early so that we'll
3923 be ready for the next challenge */
3924 StartChessProgram(&first);
3926 /* Send "new" early, in case this command takes
3927 a long time to finish, so that we'll be ready
3928 for the next challenge. */
3929 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3933 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3937 if (looking_at(buf, &i, "Removing game * from observation") ||
3938 looking_at(buf, &i, "no longer observing game *") ||
3939 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940 if (gameMode == IcsObserving &&
3941 atoi(star_match[0]) == ics_gamenum)
3943 /* icsEngineAnalyze */
3944 if (appData.icsEngineAnalyze) {
3951 ics_user_moved = FALSE;
3956 if (looking_at(buf, &i, "no longer examining game *")) {
3957 if (gameMode == IcsExamining &&
3958 atoi(star_match[0]) == ics_gamenum)
3962 ics_user_moved = FALSE;
3967 /* Advance leftover_start past any newlines we find,
3968 so only partial lines can get reparsed */
3969 if (looking_at(buf, &i, "\n")) {
3970 prevColor = curColor;
3971 if (curColor != ColorNormal) {
3972 if (oldi > next_out) {
3973 SendToPlayer(&buf[next_out], oldi - next_out);
3976 Colorize(ColorNormal, FALSE);
3977 curColor = ColorNormal;
3979 if (started == STARTED_BOARD) {
3980 started = STARTED_NONE;
3981 parse[parse_pos] = NULLCHAR;
3982 ParseBoard12(parse);
3985 /* Send premove here */
3986 if (appData.premove) {
3988 if (currentMove == 0 &&
3989 gameMode == IcsPlayingWhite &&
3990 appData.premoveWhite) {
3991 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992 if (appData.debugMode)
3993 fprintf(debugFP, "Sending premove:\n");
3995 } else if (currentMove == 1 &&
3996 gameMode == IcsPlayingBlack &&
3997 appData.premoveBlack) {
3998 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999 if (appData.debugMode)
4000 fprintf(debugFP, "Sending premove:\n");
4002 } else if (gotPremove) {
4004 ClearPremoveHighlights();
4005 if (appData.debugMode)
4006 fprintf(debugFP, "Sending premove:\n");
4007 UserMoveEvent(premoveFromX, premoveFromY,
4008 premoveToX, premoveToY,
4013 /* Usually suppress following prompt */
4014 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016 if (looking_at(buf, &i, "*% ")) {
4017 savingComment = FALSE;
4022 } else if (started == STARTED_HOLDINGS) {
4024 char new_piece[MSG_SIZ];
4025 started = STARTED_NONE;
4026 parse[parse_pos] = NULLCHAR;
4027 if (appData.debugMode)
4028 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029 parse, currentMove);
4030 if (sscanf(parse, " game %d", &gamenum) == 1) {
4031 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032 if (gameInfo.variant == VariantNormal) {
4033 /* [HGM] We seem to switch variant during a game!
4034 * Presumably no holdings were displayed, so we have
4035 * to move the position two files to the right to
4036 * create room for them!
4038 VariantClass newVariant;
4039 switch(gameInfo.boardWidth) { // base guess on board width
4040 case 9: newVariant = VariantShogi; break;
4041 case 10: newVariant = VariantGreat; break;
4042 default: newVariant = VariantCrazyhouse; break;
4044 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045 /* Get a move list just to see the header, which
4046 will tell us whether this is really bug or zh */
4047 if (ics_getting_history == H_FALSE) {
4048 ics_getting_history = H_REQUESTED;
4049 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4053 new_piece[0] = NULLCHAR;
4054 sscanf(parse, "game %d white [%s black [%s <- %s",
4055 &gamenum, white_holding, black_holding,
4057 white_holding[strlen(white_holding)-1] = NULLCHAR;
4058 black_holding[strlen(black_holding)-1] = NULLCHAR;
4059 /* [HGM] copy holdings to board holdings area */
4060 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4064 if (appData.zippyPlay && first.initDone) {
4065 ZippyHoldings(white_holding, black_holding,
4069 if (tinyLayout || smallLayout) {
4070 char wh[16], bh[16];
4071 PackHolding(wh, white_holding);
4072 PackHolding(bh, black_holding);
4073 snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074 gameInfo.white, gameInfo.black);
4076 snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077 gameInfo.white, white_holding, _("vs."),
4078 gameInfo.black, black_holding);
4080 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081 DrawPosition(FALSE, boards[currentMove]);
4083 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084 sscanf(parse, "game %d white [%s black [%s <- %s",
4085 &gamenum, white_holding, black_holding,
4087 white_holding[strlen(white_holding)-1] = NULLCHAR;
4088 black_holding[strlen(black_holding)-1] = NULLCHAR;
4089 /* [HGM] copy holdings to partner-board holdings area */
4090 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4097 /* Suppress following prompt */
4098 if (looking_at(buf, &i, "*% ")) {
4099 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100 savingComment = FALSE;
4108 i++; /* skip unparsed character and loop back */
4111 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 // SendToPlayer(&buf[next_out], i - next_out);
4114 started != STARTED_HOLDINGS && leftover_start > next_out) {
4115 SendToPlayer(&buf[next_out], leftover_start - next_out);
4119 leftover_len = buf_len - leftover_start;
4120 /* if buffer ends with something we couldn't parse,
4121 reparse it after appending the next read */
4123 } else if (count == 0) {
4124 RemoveInputSource(isr);
4125 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4127 DisplayFatalError(_("Error reading from ICS"), error, 1);
4132 /* Board style 12 looks like this:
4134 <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
4136 * The "<12> " is stripped before it gets to this routine. The two
4137 * trailing 0's (flip state and clock ticking) are later addition, and
4138 * some chess servers may not have them, or may have only the first.
4139 * Additional trailing fields may be added in the future.
4142 #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"
4144 #define RELATION_OBSERVING_PLAYED 0
4145 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE 1
4147 #define RELATION_PLAYING_NOTMYMOVE -1
4148 #define RELATION_EXAMINING 2
4149 #define RELATION_ISOLATED_BOARD -3
4150 #define RELATION_STARTING_POSITION -4 /* FICS only */
4153 ParseBoard12 (char *string)
4155 GameMode newGameMode;
4156 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159 char to_play, board_chars[200];
4160 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161 char black[32], white[32];
4163 int prevMove = currentMove;
4166 int fromX, fromY, toX, toY;
4168 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169 char *bookHit = NULL; // [HGM] book
4170 Boolean weird = FALSE, reqFlag = FALSE;
4172 fromX = fromY = toX = toY = -1;
4176 if (appData.debugMode)
4177 fprintf(debugFP, _("Parsing board: %s\n"), string);
4179 move_str[0] = NULLCHAR;
4180 elapsed_time[0] = NULLCHAR;
4181 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4183 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184 if(string[i] == ' ') { ranks++; files = 0; }
4186 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4189 for(j = 0; j <i; j++) board_chars[j] = string[j];
4190 board_chars[i] = '\0';
4193 n = sscanf(string, PATTERN, &to_play, &double_push,
4194 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195 &gamenum, white, black, &relation, &basetime, &increment,
4196 &white_stren, &black_stren, &white_time, &black_time,
4197 &moveNum, str, elapsed_time, move_str, &ics_flip,
4201 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202 DisplayError(str, 0);
4206 /* Convert the move number to internal form */
4207 moveNum = (moveNum - 1) * 2;
4208 if (to_play == 'B') moveNum++;
4209 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4216 case RELATION_OBSERVING_PLAYED:
4217 case RELATION_OBSERVING_STATIC:
4218 if (gamenum == -1) {
4219 /* Old ICC buglet */
4220 relation = RELATION_OBSERVING_STATIC;
4222 newGameMode = IcsObserving;
4224 case RELATION_PLAYING_MYMOVE:
4225 case RELATION_PLAYING_NOTMYMOVE:
4227 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228 IcsPlayingWhite : IcsPlayingBlack;
4229 soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4231 case RELATION_EXAMINING:
4232 newGameMode = IcsExamining;
4234 case RELATION_ISOLATED_BOARD:
4236 /* Just display this board. If user was doing something else,
4237 we will forget about it until the next board comes. */
4238 newGameMode = IcsIdle;
4240 case RELATION_STARTING_POSITION:
4241 newGameMode = gameMode;
4245 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4246 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248 int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4250 for (k = 0; k < ranks; k++) {
4251 for (j = 0; j < files; j++)
4252 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4253 if(gameInfo.holdingsWidth > 1) {
4254 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4255 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4258 CopyBoard(partnerBoard, board);
4259 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4260 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4261 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4262 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4263 if(toSqr = strchr(str, '-')) {
4264 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4265 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4266 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4267 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4268 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4269 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271 DisplayWhiteClock(white_time*fac, to_play == 'W');
4272 DisplayBlackClock(black_time*fac, to_play != 'W');
4273 activePartner = to_play;
4274 activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4275 partnerUp = 0; flipView = !flipView; } // [HGM] dual
4276 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4277 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4278 DisplayMessage(partnerStatus, "");
4279 partnerBoardValid = TRUE;
4283 /* Modify behavior for initial board display on move listing
4286 switch (ics_getting_history) {
4290 case H_GOT_REQ_HEADER:
4291 case H_GOT_UNREQ_HEADER:
4292 /* This is the initial position of the current game */
4293 gamenum = ics_gamenum;
4294 moveNum = 0; /* old ICS bug workaround */
4295 if (to_play == 'B') {
4296 startedFromSetupPosition = TRUE;
4297 blackPlaysFirst = TRUE;
4299 if (forwardMostMove == 0) forwardMostMove = 1;
4300 if (backwardMostMove == 0) backwardMostMove = 1;
4301 if (currentMove == 0) currentMove = 1;
4303 newGameMode = gameMode;
4304 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4306 case H_GOT_UNWANTED_HEADER:
4307 /* This is an initial board that we don't want */
4309 case H_GETTING_MOVES:
4310 /* Should not happen */
4311 DisplayError(_("Error gathering move list: extra board"), 0);
4312 ics_getting_history = H_FALSE;
4316 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4317 move_str[1] == '@' && !gameInfo.holdingsWidth ||
4318 weird && (int)gameInfo.variant < (int)VariantShogi) {
4319 /* [HGM] We seem to have switched variant unexpectedly
4320 * Try to guess new variant from board size
4322 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4323 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4324 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4325 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4326 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4327 if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4328 if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4329 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4330 /* Get a move list just to see the header, which
4331 will tell us whether this is really bug or zh */
4332 if (ics_getting_history == H_FALSE) {
4333 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4334 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4339 /* Take action if this is the first board of a new game, or of a
4340 different game than is currently being displayed. */
4341 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4342 relation == RELATION_ISOLATED_BOARD) {
4344 /* Forget the old game and get the history (if any) of the new one */
4345 if (gameMode != BeginningOfGame) {
4349 if (appData.autoRaiseBoard) BoardToTop();
4351 if (gamenum == -1) {
4352 newGameMode = IcsIdle;
4353 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4354 appData.getMoveList && !reqFlag) {
4355 /* Need to get game history */
4356 ics_getting_history = H_REQUESTED;
4357 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4361 /* Initially flip the board to have black on the bottom if playing
4362 black or if the ICS flip flag is set, but let the user change
4363 it with the Flip View button. */
4364 flipView = appData.autoFlipView ?
4365 (newGameMode == IcsPlayingBlack) || ics_flip :
4368 /* Done with values from previous mode; copy in new ones */
4369 gameMode = newGameMode;
4371 ics_gamenum = gamenum;
4372 if (gamenum == gs_gamenum) {
4373 int klen = strlen(gs_kind);
4374 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4375 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4376 gameInfo.event = StrSave(str);
4378 gameInfo.event = StrSave("ICS game");
4380 gameInfo.site = StrSave(appData.icsHost);
4381 gameInfo.date = PGNDate();
4382 gameInfo.round = StrSave("-");
4383 gameInfo.white = StrSave(white);
4384 gameInfo.black = StrSave(black);
4385 timeControl = basetime * 60 * 1000;
4387 timeIncrement = increment * 1000;
4388 movesPerSession = 0;
4389 gameInfo.timeControl = TimeControlTagValue();
4390 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4391 if (appData.debugMode) {
4392 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4393 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4394 setbuf(debugFP, NULL);
4397 gameInfo.outOfBook = NULL;
4399 /* Do we have the ratings? */
4400 if (strcmp(player1Name, white) == 0 &&
4401 strcmp(player2Name, black) == 0) {
4402 if (appData.debugMode)
4403 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4404 player1Rating, player2Rating);
4405 gameInfo.whiteRating = player1Rating;
4406 gameInfo.blackRating = player2Rating;
4407 } else if (strcmp(player2Name, white) == 0 &&
4408 strcmp(player1Name, black) == 0) {
4409 if (appData.debugMode)
4410 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4411 player2Rating, player1Rating);
4412 gameInfo.whiteRating = player2Rating;
4413 gameInfo.blackRating = player1Rating;
4415 player1Name[0] = player2Name[0] = NULLCHAR;
4417 /* Silence shouts if requested */
4418 if (appData.quietPlay &&
4419 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4420 SendToICS(ics_prefix);
4421 SendToICS("set shout 0\n");
4425 /* Deal with midgame name changes */
4427 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4428 if (gameInfo.white) free(gameInfo.white);
4429 gameInfo.white = StrSave(white);
4431 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4432 if (gameInfo.black) free(gameInfo.black);
4433 gameInfo.black = StrSave(black);
4437 /* Throw away game result if anything actually changes in examine mode */
4438 if (gameMode == IcsExamining && !newGame) {
4439 gameInfo.result = GameUnfinished;
4440 if (gameInfo.resultDetails != NULL) {
4441 free(gameInfo.resultDetails);
4442 gameInfo.resultDetails = NULL;
4446 /* In pausing && IcsExamining mode, we ignore boards coming
4447 in if they are in a different variation than we are. */
4448 if (pauseExamInvalid) return;
4449 if (pausing && gameMode == IcsExamining) {
4450 if (moveNum <= pauseExamForwardMostMove) {
4451 pauseExamInvalid = TRUE;
4452 forwardMostMove = pauseExamForwardMostMove;
4457 if (appData.debugMode) {
4458 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4460 /* Parse the board */
4461 for (k = 0; k < ranks; k++) {
4462 for (j = 0; j < files; j++)
4463 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4464 if(gameInfo.holdingsWidth > 1) {
4465 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4466 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4469 if(moveNum==0 && gameInfo.variant == VariantSChess) {
4470 board[5][BOARD_RGHT+1] = WhiteAngel;
4471 board[6][BOARD_RGHT+1] = WhiteMarshall;
4472 board[1][0] = BlackMarshall;
4473 board[2][0] = BlackAngel;
4474 board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4476 CopyBoard(boards[moveNum], board);
4477 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4479 startedFromSetupPosition =
4480 !CompareBoards(board, initialPosition);
4481 if(startedFromSetupPosition)
4482 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4485 /* [HGM] Set castling rights. Take the outermost Rooks,
4486 to make it also work for FRC opening positions. Note that board12
4487 is really defective for later FRC positions, as it has no way to
4488 indicate which Rook can castle if they are on the same side of King.
4489 For the initial position we grant rights to the outermost Rooks,
4490 and remember thos rights, and we then copy them on positions
4491 later in an FRC game. This means WB might not recognize castlings with
4492 Rooks that have moved back to their original position as illegal,
4493 but in ICS mode that is not its job anyway.
4495 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4496 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4498 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499 if(board[0][i] == WhiteRook) j = i;
4500 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502 if(board[0][i] == WhiteRook) j = i;
4503 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4505 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4506 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4507 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4508 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4509 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4511 boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4512 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4513 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4514 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4515 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4516 if(board[BOARD_HEIGHT-1][k] == bKing)
4517 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4518 if(gameInfo.variant == VariantTwoKings) {
4519 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4520 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4521 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4524 r = boards[moveNum][CASTLING][0] = initialRights[0];
4525 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4526 r = boards[moveNum][CASTLING][1] = initialRights[1];
4527 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4528 r = boards[moveNum][CASTLING][3] = initialRights[3];
4529 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4530 r = boards[moveNum][CASTLING][4] = initialRights[4];
4531 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4532 /* wildcastle kludge: always assume King has rights */
4533 r = boards[moveNum][CASTLING][2] = initialRights[2];
4534 r = boards[moveNum][CASTLING][5] = initialRights[5];
4536 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4537 boards[moveNum][EP_STATUS] = EP_NONE;
4538 if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4539 if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4540 if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4543 if (ics_getting_history == H_GOT_REQ_HEADER ||
4544 ics_getting_history == H_GOT_UNREQ_HEADER) {
4545 /* This was an initial position from a move list, not
4546 the current position */
4550 /* Update currentMove and known move number limits */
4551 newMove = newGame || moveNum > forwardMostMove;
4554 forwardMostMove = backwardMostMove = currentMove = moveNum;
4555 if (gameMode == IcsExamining && moveNum == 0) {
4556 /* Workaround for ICS limitation: we are not told the wild
4557 type when starting to examine a game. But if we ask for
4558 the move list, the move list header will tell us */
4559 ics_getting_history = H_REQUESTED;
4560 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4563 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4564 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4566 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4567 /* [HGM] applied this also to an engine that is silently watching */
4568 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4569 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4570 gameInfo.variant == currentlyInitializedVariant) {
4571 takeback = forwardMostMove - moveNum;
4572 for (i = 0; i < takeback; i++) {
4573 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4574 SendToProgram("undo\n", &first);
4579 forwardMostMove = moveNum;
4580 if (!pausing || currentMove > forwardMostMove)
4581 currentMove = forwardMostMove;
4583 /* New part of history that is not contiguous with old part */
4584 if (pausing && gameMode == IcsExamining) {
4585 pauseExamInvalid = TRUE;
4586 forwardMostMove = pauseExamForwardMostMove;
4589 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4591 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4592 // [HGM] when we will receive the move list we now request, it will be
4593 // fed to the engine from the first move on. So if the engine is not
4594 // in the initial position now, bring it there.
4595 InitChessProgram(&first, 0);
4598 ics_getting_history = H_REQUESTED;
4599 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4602 forwardMostMove = backwardMostMove = currentMove = moveNum;
4605 /* Update the clocks */
4606 if (strchr(elapsed_time, '.')) {
4608 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4609 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4611 /* Time is in seconds */
4612 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4613 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4618 if (appData.zippyPlay && newGame &&
4619 gameMode != IcsObserving && gameMode != IcsIdle &&
4620 gameMode != IcsExamining)
4621 ZippyFirstBoard(moveNum, basetime, increment);
4624 /* Put the move on the move list, first converting
4625 to canonical algebraic form. */
4627 if (appData.debugMode) {
4628 if (appData.debugMode) { int f = forwardMostMove;
4629 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4630 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4631 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4633 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4634 fprintf(debugFP, "moveNum = %d\n", moveNum);
4635 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4636 setbuf(debugFP, NULL);
4638 if (moveNum <= backwardMostMove) {
4639 /* We don't know what the board looked like before
4641 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4642 strcat(parseList[moveNum - 1], " ");
4643 strcat(parseList[moveNum - 1], elapsed_time);
4644 moveList[moveNum - 1][0] = NULLCHAR;
4645 } else if (strcmp(move_str, "none") == 0) {
4646 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4647 /* Again, we don't know what the board looked like;
4648 this is really the start of the game. */
4649 parseList[moveNum - 1][0] = NULLCHAR;
4650 moveList[moveNum - 1][0] = NULLCHAR;
4651 backwardMostMove = moveNum;
4652 startedFromSetupPosition = TRUE;
4653 fromX = fromY = toX = toY = -1;
4655 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4656 // So we parse the long-algebraic move string in stead of the SAN move
4657 int valid; char buf[MSG_SIZ], *prom;
4659 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4660 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4661 // str looks something like "Q/a1-a2"; kill the slash
4663 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4664 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4665 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4666 strcat(buf, prom); // long move lacks promo specification!
4667 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4668 if(appData.debugMode)
4669 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4670 safeStrCpy(move_str, buf, MSG_SIZ);
4672 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4673 &fromX, &fromY, &toX, &toY, &promoChar)
4674 || ParseOneMove(buf, moveNum - 1, &moveType,
4675 &fromX, &fromY, &toX, &toY, &promoChar);
4676 // end of long SAN patch
4678 (void) CoordsToAlgebraic(boards[moveNum - 1],
4679 PosFlags(moveNum - 1),
4680 fromY, fromX, toY, toX, promoChar,
4681 parseList[moveNum-1]);
4682 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4688 if(gameInfo.variant != VariantShogi)
4689 strcat(parseList[moveNum - 1], "+");
4692 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4693 strcat(parseList[moveNum - 1], "#");
4696 strcat(parseList[moveNum - 1], " ");
4697 strcat(parseList[moveNum - 1], elapsed_time);
4698 /* currentMoveString is set as a side-effect of ParseOneMove */
4699 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4700 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4701 strcat(moveList[moveNum - 1], "\n");
4703 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4704 && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4705 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4706 ChessSquare old, new = boards[moveNum][k][j];
4707 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4708 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4709 if(old == new) continue;
4710 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4711 else if(new == WhiteWazir || new == BlackWazir) {
4712 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4713 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4714 else boards[moveNum][k][j] = old; // preserve type of Gold
4715 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4716 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4719 /* Move from ICS was illegal!? Punt. */
4720 if (appData.debugMode) {
4721 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4722 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4724 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4725 strcat(parseList[moveNum - 1], " ");
4726 strcat(parseList[moveNum - 1], elapsed_time);
4727 moveList[moveNum - 1][0] = NULLCHAR;
4728 fromX = fromY = toX = toY = -1;
4731 if (appData.debugMode) {
4732 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4733 setbuf(debugFP, NULL);
4737 /* Send move to chess program (BEFORE animating it). */
4738 if (appData.zippyPlay && !newGame && newMove &&
4739 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4741 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4742 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4743 if (moveList[moveNum - 1][0] == NULLCHAR) {
4744 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4746 DisplayError(str, 0);
4748 if (first.sendTime) {
4749 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4751 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4752 if (firstMove && !bookHit) {
4754 if (first.useColors) {
4755 SendToProgram(gameMode == IcsPlayingWhite ?
4757 "black\ngo\n", &first);
4759 SendToProgram("go\n", &first);
4761 first.maybeThinking = TRUE;
4764 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4765 if (moveList[moveNum - 1][0] == NULLCHAR) {
4766 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4767 DisplayError(str, 0);
4769 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4770 SendMoveToProgram(moveNum - 1, &first);
4777 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4778 /* If move comes from a remote source, animate it. If it
4779 isn't remote, it will have already been animated. */
4780 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4781 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4783 if (!pausing && appData.highlightLastMove) {
4784 SetHighlights(fromX, fromY, toX, toY);
4788 /* Start the clocks */
4789 whiteFlag = blackFlag = FALSE;
4790 appData.clockMode = !(basetime == 0 && increment == 0);
4792 ics_clock_paused = TRUE;
4794 } else if (ticking == 1) {
4795 ics_clock_paused = FALSE;
4797 if (gameMode == IcsIdle ||
4798 relation == RELATION_OBSERVING_STATIC ||
4799 relation == RELATION_EXAMINING ||
4801 DisplayBothClocks();
4805 /* Display opponents and material strengths */
4806 if (gameInfo.variant != VariantBughouse &&
4807 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4808 if (tinyLayout || smallLayout) {
4809 if(gameInfo.variant == VariantNormal)
4810 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4811 gameInfo.white, white_stren, gameInfo.black, black_stren,
4812 basetime, increment);
4814 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4815 gameInfo.white, white_stren, gameInfo.black, black_stren,
4816 basetime, increment, (int) gameInfo.variant);
4818 if(gameInfo.variant == VariantNormal)
4819 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4820 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4821 basetime, increment);
4823 snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4824 gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4825 basetime, increment, VariantName(gameInfo.variant));
4828 if (appData.debugMode) {
4829 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4834 /* Display the board */
4835 if (!pausing && !appData.noGUI) {
4837 if (appData.premove)
4839 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4840 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4841 ClearPremoveHighlights();
4843 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4844 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4845 DrawPosition(j, boards[currentMove]);
4847 DisplayMove(moveNum - 1);
4848 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4849 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4850 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4851 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4855 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4857 if(bookHit) { // [HGM] book: simulate book reply
4858 static char bookMove[MSG_SIZ]; // a bit generous?
4860 programStats.nodes = programStats.depth = programStats.time =
4861 programStats.score = programStats.got_only_move = 0;
4862 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4864 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4865 strcat(bookMove, bookHit);
4866 HandleMachineMove(bookMove, &first);
4875 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4876 ics_getting_history = H_REQUESTED;
4877 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4883 AnalysisPeriodicEvent (int force)
4885 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4886 && !force) || !appData.periodicUpdates)
4889 /* Send . command to Crafty to collect stats */
4890 SendToProgram(".\n", &first);
4892 /* Don't send another until we get a response (this makes
4893 us stop sending to old Crafty's which don't understand
4894 the "." command (sending illegal cmds resets node count & time,
4895 which looks bad)) */
4896 programStats.ok_to_send = 0;
4900 ics_update_width (int new_width)
4902 ics_printf("set width %d\n", new_width);
4906 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4910 if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4911 // null move in variant where engine does not understand it (for analysis purposes)
4912 SendBoard(cps, moveNum + 1); // send position after move in stead.
4915 if (cps->useUsermove) {
4916 SendToProgram("usermove ", cps);
4920 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4921 int len = space - parseList[moveNum];
4922 memcpy(buf, parseList[moveNum], len);
4924 buf[len] = NULLCHAR;
4926 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4928 SendToProgram(buf, cps);
4930 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4931 AlphaRank(moveList[moveNum], 4);
4932 SendToProgram(moveList[moveNum], cps);
4933 AlphaRank(moveList[moveNum], 4); // and back
4935 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4936 * the engine. It would be nice to have a better way to identify castle
4938 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4939 && cps->useOOCastle) {
4940 int fromX = moveList[moveNum][0] - AAA;
4941 int fromY = moveList[moveNum][1] - ONE;
4942 int toX = moveList[moveNum][2] - AAA;
4943 int toY = moveList[moveNum][3] - ONE;
4944 if((boards[moveNum][fromY][fromX] == WhiteKing
4945 && boards[moveNum][toY][toX] == WhiteRook)
4946 || (boards[moveNum][fromY][fromX] == BlackKing
4947 && boards[moveNum][toY][toX] == BlackRook)) {
4948 if(toX > fromX) SendToProgram("O-O\n", cps);
4949 else SendToProgram("O-O-O\n", cps);
4951 else SendToProgram(moveList[moveNum], cps);
4953 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4954 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4955 if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4956 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4957 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4959 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4960 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4961 SendToProgram(buf, cps);
4963 else SendToProgram(moveList[moveNum], cps);
4964 /* End of additions by Tord */
4967 /* [HGM] setting up the opening has brought engine in force mode! */
4968 /* Send 'go' if we are in a mode where machine should play. */
4969 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4970 (gameMode == TwoMachinesPlay ||
4972 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4974 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4975 SendToProgram("go\n", cps);
4976 if (appData.debugMode) {
4977 fprintf(debugFP, "(extra)\n");
4980 setboardSpoiledMachineBlack = 0;
4984 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4986 char user_move[MSG_SIZ];
4989 if(gameInfo.variant == VariantSChess && promoChar) {
4990 snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4991 if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4992 } else suffix[0] = NULLCHAR;
4996 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4997 (int)moveType, fromX, fromY, toX, toY);
4998 DisplayError(user_move + strlen("say "), 0);
5000 case WhiteKingSideCastle:
5001 case BlackKingSideCastle:
5002 case WhiteQueenSideCastleWild:
5003 case BlackQueenSideCastleWild:
5005 case WhiteHSideCastleFR:
5006 case BlackHSideCastleFR:
5008 snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5010 case WhiteQueenSideCastle:
5011 case BlackQueenSideCastle:
5012 case WhiteKingSideCastleWild:
5013 case BlackKingSideCastleWild:
5015 case WhiteASideCastleFR:
5016 case BlackASideCastleFR:
5018 snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5020 case WhiteNonPromotion:
5021 case BlackNonPromotion:
5022 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5024 case WhitePromotion:
5025 case BlackPromotion:
5026 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5027 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5028 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5029 PieceToChar(WhiteFerz));
5030 else if(gameInfo.variant == VariantGreat)
5031 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5032 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5033 PieceToChar(WhiteMan));
5035 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5036 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5042 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5043 ToUpper(PieceToChar((ChessSquare) fromX)),
5044 AAA + toX, ONE + toY);
5046 case IllegalMove: /* could be a variant we don't quite understand */
5047 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5049 case WhiteCapturesEnPassant:
5050 case BlackCapturesEnPassant:
5051 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5052 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5055 SendToICS(user_move);
5056 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5057 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5062 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5063 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5064 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5065 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5066 DisplayError(_("You cannot do this while you are playing or observing"), 0);
5069 if(gameMode != IcsExamining) { // is this ever not the case?
5070 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5072 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5073 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5074 } else { // on FICS we must first go to general examine mode
5075 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5077 if(gameInfo.variant != VariantNormal) {
5078 // try figure out wild number, as xboard names are not always valid on ICS
5079 for(i=1; i<=36; i++) {
5080 snprintf(buf, MSG_SIZ, "wild/%d", i);
5081 if(StringToVariant(buf) == gameInfo.variant) break;
5083 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5084 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5085 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5086 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5087 SendToICS(ics_prefix);
5089 if(startedFromSetupPosition || backwardMostMove != 0) {
5090 fen = PositionToFEN(backwardMostMove, NULL);
5091 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5092 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5094 } else { // FICS: everything has to set by separate bsetup commands
5095 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5096 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5098 if(!WhiteOnMove(backwardMostMove)) {
5099 SendToICS("bsetup tomove black\n");
5101 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5102 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5104 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5105 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5107 i = boards[backwardMostMove][EP_STATUS];
5108 if(i >= 0) { // set e.p.
5109 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5115 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5116 SendToICS("bsetup done\n"); // switch to normal examining.
5118 for(i = backwardMostMove; i<last; i++) {
5120 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5123 SendToICS(ics_prefix);
5124 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5128 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5130 if (rf == DROP_RANK) {
5131 if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5132 sprintf(move, "%c@%c%c\n",
5133 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5135 if (promoChar == 'x' || promoChar == NULLCHAR) {
5136 sprintf(move, "%c%c%c%c\n",
5137 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5139 sprintf(move, "%c%c%c%c%c\n",
5140 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5146 ProcessICSInitScript (FILE *f)
5150 while (fgets(buf, MSG_SIZ, f)) {
5151 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5158 static int lastX, lastY, selectFlag, dragging;
5163 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5164 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5165 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5166 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5167 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5168 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5171 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5172 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5173 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5174 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5175 if(!step) step = -1;
5176 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5177 appData.testLegality && (promoSweep == king ||
5178 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5180 int victim = boards[currentMove][toY][toX];
5181 boards[currentMove][toY][toX] = promoSweep;
5182 DrawPosition(FALSE, boards[currentMove]);
5183 boards[currentMove][toY][toX] = victim;
5185 ChangeDragPiece(promoSweep);
5189 PromoScroll (int x, int y)
5193 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5194 if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5195 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5196 if(!step) return FALSE;
5197 lastX = x; lastY = y;
5198 if((promoSweep < BlackPawn) == flipView) step = -step;
5199 if(step > 0) selectFlag = 1;
5200 if(!selectFlag) Sweep(step);
5205 NextPiece (int step)
5207 ChessSquare piece = boards[currentMove][toY][toX];
5210 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5211 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5212 if(!step) step = -1;
5213 } while(PieceToChar(pieceSweep) == '.');
5214 boards[currentMove][toY][toX] = pieceSweep;
5215 DrawPosition(FALSE, boards[currentMove]);
5216 boards[currentMove][toY][toX] = piece;
5218 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5220 AlphaRank (char *move, int n)
5222 // char *p = move, c; int x, y;
5224 if (appData.debugMode) {
5225 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5229 move[2]>='0' && move[2]<='9' &&
5230 move[3]>='a' && move[3]<='x' ) {
5232 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5233 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5235 if(move[0]>='0' && move[0]<='9' &&
5236 move[1]>='a' && move[1]<='x' &&
5237 move[2]>='0' && move[2]<='9' &&
5238 move[3]>='a' && move[3]<='x' ) {
5239 /* input move, Shogi -> normal */
5240 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5241 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5242 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5243 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5246 move[3]>='0' && move[3]<='9' &&
5247 move[2]>='a' && move[2]<='x' ) {
5249 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5250 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5253 move[0]>='a' && move[0]<='x' &&
5254 move[3]>='0' && move[3]<='9' &&
5255 move[2]>='a' && move[2]<='x' ) {
5256 /* output move, normal -> Shogi */
5257 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5258 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5259 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5260 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5261 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5263 if (appData.debugMode) {
5264 fprintf(debugFP, " out = '%s'\n", move);
5268 char yy_textstr[8000];
5270 /* Parser for moves from gnuchess, ICS, or user typein box */
5272 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5274 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5276 switch (*moveType) {
5277 case WhitePromotion:
5278 case BlackPromotion:
5279 case WhiteNonPromotion:
5280 case BlackNonPromotion:
5282 case WhiteCapturesEnPassant:
5283 case BlackCapturesEnPassant:
5284 case WhiteKingSideCastle:
5285 case WhiteQueenSideCastle:
5286 case BlackKingSideCastle:
5287 case BlackQueenSideCastle:
5288 case WhiteKingSideCastleWild:
5289 case WhiteQueenSideCastleWild:
5290 case BlackKingSideCastleWild:
5291 case BlackQueenSideCastleWild:
5292 /* Code added by Tord: */
5293 case WhiteHSideCastleFR:
5294 case WhiteASideCastleFR:
5295 case BlackHSideCastleFR:
5296 case BlackASideCastleFR:
5297 /* End of code added by Tord */
5298 case IllegalMove: /* bug or odd chess variant */
5299 *fromX = currentMoveString[0] - AAA;
5300 *fromY = currentMoveString[1] - ONE;
5301 *toX = currentMoveString[2] - AAA;
5302 *toY = currentMoveString[3] - ONE;
5303 *promoChar = currentMoveString[4];
5304 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5305 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5306 if (appData.debugMode) {
5307 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5309 *fromX = *fromY = *toX = *toY = 0;
5312 if (appData.testLegality) {
5313 return (*moveType != IllegalMove);
5315 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5316 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5321 *fromX = *moveType == WhiteDrop ?
5322 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5323 (int) CharToPiece(ToLower(currentMoveString[0]));
5325 *toX = currentMoveString[2] - AAA;
5326 *toY = currentMoveString[3] - ONE;
5327 *promoChar = NULLCHAR;
5331 case ImpossibleMove:
5341 if (appData.debugMode) {
5342 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5345 *fromX = *fromY = *toX = *toY = 0;
5346 *promoChar = NULLCHAR;
5351 Boolean pushed = FALSE;
5352 char *lastParseAttempt;
5355 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5356 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5357 int fromX, fromY, toX, toY; char promoChar;
5362 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5363 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5366 endPV = forwardMostMove;
5368 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5369 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5370 lastParseAttempt = pv;
5371 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5372 if(!valid && nr == 0 &&
5373 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5374 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5375 // Hande case where played move is different from leading PV move
5376 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5377 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5378 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5379 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5380 endPV += 2; // if position different, keep this
5381 moveList[endPV-1][0] = fromX + AAA;
5382 moveList[endPV-1][1] = fromY + ONE;
5383 moveList[endPV-1][2] = toX + AAA;
5384 moveList[endPV-1][3] = toY + ONE;
5385 parseList[endPV-1][0] = NULLCHAR;
5386 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5389 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5390 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5391 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5392 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5393 valid++; // allow comments in PV
5397 if(endPV+1 > framePtr) break; // no space, truncate
5400 CopyBoard(boards[endPV], boards[endPV-1]);
5401 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5402 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5403 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5404 CoordsToAlgebraic(boards[endPV - 1],
5405 PosFlags(endPV - 1),
5406 fromY, fromX, toY, toX, promoChar,
5407 parseList[endPV - 1]);
5409 if(atEnd == 2) return; // used hidden, for PV conversion
5410 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5411 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5412 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5413 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5414 DrawPosition(TRUE, boards[currentMove]);
5418 MultiPV (ChessProgramState *cps)
5419 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5421 for(i=0; i<cps->nrOptions; i++)
5422 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5428 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5430 int startPV, multi, lineStart, origIndex = index;
5431 char *p, buf2[MSG_SIZ];
5433 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5434 lastX = x; lastY = y;
5435 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5436 lineStart = startPV = index;
5437 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5438 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5440 do{ while(buf[index] && buf[index] != '\n') index++;
5441 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5443 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5444 int n = first.option[multi].value;
5445 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5446 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5447 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5448 first.option[multi].value = n;
5451 } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5452 ExcludeClick(origIndex - lineStart);
5455 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5456 *start = startPV; *end = index-1;
5463 static char buf[10*MSG_SIZ];
5464 int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5466 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5467 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5468 for(i = forwardMostMove; i<endPV; i++){
5469 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5470 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5473 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5474 if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5480 LoadPV (int x, int y)
5481 { // called on right mouse click to load PV
5482 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5483 lastX = x; lastY = y;
5484 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5491 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5492 if(endPV < 0) return;
5493 if(appData.autoCopyPV) CopyFENToClipboard();
5495 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5496 Boolean saveAnimate = appData.animate;
5498 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5499 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5500 } else storedGames--; // abandon shelved tail of original game
5503 forwardMostMove = currentMove;
5504 currentMove = oldFMM;
5505 appData.animate = FALSE;
5506 ToNrEvent(forwardMostMove);
5507 appData.animate = saveAnimate;
5509 currentMove = forwardMostMove;
5510 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5511 ClearPremoveHighlights();
5512 DrawPosition(TRUE, boards[currentMove]);
5516 MovePV (int x, int y, int h)
5517 { // step through PV based on mouse coordinates (called on mouse move)
5518 int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5520 // we must somehow check if right button is still down (might be released off board!)
5521 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5522 if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5523 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5525 lastX = x; lastY = y;
5527 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5528 if(endPV < 0) return;
5529 if(y < margin) step = 1; else
5530 if(y > h - margin) step = -1;
5531 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5532 currentMove += step;
5533 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5534 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5535 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5536 DrawPosition(FALSE, boards[currentMove]);
5540 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5541 // All positions will have equal probability, but the current method will not provide a unique
5542 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5548 int piecesLeft[(int)BlackPawn];
5549 int seed, nrOfShuffles;
5552 GetPositionNumber ()
5553 { // sets global variable seed
5556 seed = appData.defaultFrcPosition;
5557 if(seed < 0) { // randomize based on time for negative FRC position numbers
5558 for(i=0; i<50; i++) seed += random();
5559 seed = random() ^ random() >> 8 ^ random() << 8;
5560 if(seed<0) seed = -seed;
5565 put (Board board, int pieceType, int rank, int n, int shade)
5566 // put the piece on the (n-1)-th empty squares of the given shade
5570 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5571 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5572 board[rank][i] = (ChessSquare) pieceType;
5573 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5575 piecesLeft[pieceType]--;
5584 AddOnePiece (Board board, int pieceType, int rank, int shade)
5585 // calculate where the next piece goes, (any empty square), and put it there
5589 i = seed % squaresLeft[shade];
5590 nrOfShuffles *= squaresLeft[shade];
5591 seed /= squaresLeft[shade];
5592 put(board, pieceType, rank, i, shade);
5596 AddTwoPieces (Board board, int pieceType, int rank)
5597 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5599 int i, n=squaresLeft[ANY], j=n-1, k;
5601 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5602 i = seed % k; // pick one
5605 while(i >= j) i -= j--;
5606 j = n - 1 - j; i += j;
5607 put(board, pieceType, rank, j, ANY);
5608 put(board, pieceType, rank, i, ANY);
5612 SetUpShuffle (Board board, int number)
5616 GetPositionNumber(); nrOfShuffles = 1;
5618 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5619 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5620 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5622 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5624 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5625 p = (int) board[0][i];
5626 if(p < (int) BlackPawn) piecesLeft[p] ++;
5627 board[0][i] = EmptySquare;
5630 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5631 // shuffles restricted to allow normal castling put KRR first
5632 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5633 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5634 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5635 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5636 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5637 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5638 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5639 put(board, WhiteRook, 0, 0, ANY);
5640 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5643 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5644 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5645 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5646 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5647 while(piecesLeft[p] >= 2) {
5648 AddOnePiece(board, p, 0, LITE);
5649 AddOnePiece(board, p, 0, DARK);
5651 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5654 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5655 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5656 // but we leave King and Rooks for last, to possibly obey FRC restriction
5657 if(p == (int)WhiteRook) continue;
5658 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5659 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5662 // now everything is placed, except perhaps King (Unicorn) and Rooks
5664 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5665 // Last King gets castling rights
5666 while(piecesLeft[(int)WhiteUnicorn]) {
5667 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5668 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5671 while(piecesLeft[(int)WhiteKing]) {
5672 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5673 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5678 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5679 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5682 // Only Rooks can be left; simply place them all
5683 while(piecesLeft[(int)WhiteRook]) {
5684 i = put(board, WhiteRook, 0, 0, ANY);
5685 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5688 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5690 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5693 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5694 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5697 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5701 SetCharTable (char *table, const char * map)
5702 /* [HGM] moved here from winboard.c because of its general usefulness */
5703 /* Basically a safe strcpy that uses the last character as King */
5705 int result = FALSE; int NrPieces;
5707 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5708 && NrPieces >= 12 && !(NrPieces&1)) {
5709 int i; /* [HGM] Accept even length from 12 to 34 */
5711 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5712 for( i=0; i<NrPieces/2-1; i++ ) {
5714 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5716 table[(int) WhiteKing] = map[NrPieces/2-1];
5717 table[(int) BlackKing] = map[NrPieces-1];
5726 Prelude (Board board)
5727 { // [HGM] superchess: random selection of exo-pieces
5728 int i, j, k; ChessSquare p;
5729 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5731 GetPositionNumber(); // use FRC position number
5733 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5734 SetCharTable(pieceToChar, appData.pieceToCharTable);
5735 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5736 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5739 j = seed%4; seed /= 4;
5740 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5741 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5742 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5743 j = seed%3 + (seed%3 >= j); seed /= 3;
5744 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5745 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5746 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5747 j = seed%3; seed /= 3;
5748 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5749 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5750 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5751 j = seed%2 + (seed%2 >= j); seed /= 2;
5752 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5753 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5754 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5755 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5756 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5757 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5758 put(board, exoPieces[0], 0, 0, ANY);
5759 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5763 InitPosition (int redraw)
5765 ChessSquare (* pieces)[BOARD_FILES];
5766 int i, j, pawnRow, overrule,
5767 oldx = gameInfo.boardWidth,
5768 oldy = gameInfo.boardHeight,
5769 oldh = gameInfo.holdingsWidth;
5772 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5774 /* [AS] Initialize pv info list [HGM] and game status */
5776 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5777 pvInfoList[i].depth = 0;
5778 boards[i][EP_STATUS] = EP_NONE;
5779 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5782 initialRulePlies = 0; /* 50-move counter start */
5784 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5785 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5789 /* [HGM] logic here is completely changed. In stead of full positions */
5790 /* the initialized data only consist of the two backranks. The switch */
5791 /* selects which one we will use, which is than copied to the Board */
5792 /* initialPosition, which for the rest is initialized by Pawns and */
5793 /* empty squares. This initial position is then copied to boards[0], */
5794 /* possibly after shuffling, so that it remains available. */
5796 gameInfo.holdingsWidth = 0; /* default board sizes */
5797 gameInfo.boardWidth = 8;
5798 gameInfo.boardHeight = 8;
5799 gameInfo.holdingsSize = 0;
5800 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5801 for(i=0; i<BOARD_FILES-2; i++)
5802 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5803 initialPosition[EP_STATUS] = EP_NONE;
5804 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5805 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5806 SetCharTable(pieceNickName, appData.pieceNickNames);
5807 else SetCharTable(pieceNickName, "............");
5810 switch (gameInfo.variant) {
5811 case VariantFischeRandom:
5812 shuffleOpenings = TRUE;
5815 case VariantShatranj:
5816 pieces = ShatranjArray;
5817 nrCastlingRights = 0;
5818 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5821 pieces = makrukArray;
5822 nrCastlingRights = 0;
5823 startedFromSetupPosition = TRUE;
5824 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5826 case VariantTwoKings:
5827 pieces = twoKingsArray;
5830 pieces = GrandArray;
5831 nrCastlingRights = 0;
5832 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5833 gameInfo.boardWidth = 10;
5834 gameInfo.boardHeight = 10;
5835 gameInfo.holdingsSize = 7;
5837 case VariantCapaRandom:
5838 shuffleOpenings = TRUE;
5839 case VariantCapablanca:
5840 pieces = CapablancaArray;
5841 gameInfo.boardWidth = 10;
5842 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5845 pieces = GothicArray;
5846 gameInfo.boardWidth = 10;
5847 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5850 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5851 gameInfo.holdingsSize = 7;
5854 pieces = JanusArray;
5855 gameInfo.boardWidth = 10;
5856 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5857 nrCastlingRights = 6;
5858 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5859 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5860 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5861 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5862 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5863 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5866 pieces = FalconArray;
5867 gameInfo.boardWidth = 10;
5868 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5870 case VariantXiangqi:
5871 pieces = XiangqiArray;
5872 gameInfo.boardWidth = 9;
5873 gameInfo.boardHeight = 10;
5874 nrCastlingRights = 0;
5875 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5878 pieces = ShogiArray;
5879 gameInfo.boardWidth = 9;
5880 gameInfo.boardHeight = 9;
5881 gameInfo.holdingsSize = 7;
5882 nrCastlingRights = 0;
5883 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5885 case VariantCourier:
5886 pieces = CourierArray;
5887 gameInfo.boardWidth = 12;
5888 nrCastlingRights = 0;
5889 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5891 case VariantKnightmate:
5892 pieces = KnightmateArray;
5893 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5895 case VariantSpartan:
5896 pieces = SpartanArray;
5897 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5900 pieces = fairyArray;
5901 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5904 pieces = GreatArray;
5905 gameInfo.boardWidth = 10;
5906 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5907 gameInfo.holdingsSize = 8;
5911 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5912 gameInfo.holdingsSize = 8;
5913 startedFromSetupPosition = TRUE;
5915 case VariantCrazyhouse:
5916 case VariantBughouse:
5918 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5919 gameInfo.holdingsSize = 5;
5921 case VariantWildCastle:
5923 /* !!?shuffle with kings guaranteed to be on d or e file */
5924 shuffleOpenings = 1;
5926 case VariantNoCastle:
5928 nrCastlingRights = 0;
5929 /* !!?unconstrained back-rank shuffle */
5930 shuffleOpenings = 1;
5935 if(appData.NrFiles >= 0) {
5936 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5937 gameInfo.boardWidth = appData.NrFiles;
5939 if(appData.NrRanks >= 0) {
5940 gameInfo.boardHeight = appData.NrRanks;
5942 if(appData.holdingsSize >= 0) {
5943 i = appData.holdingsSize;
5944 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5945 gameInfo.holdingsSize = i;
5947 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5948 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5949 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5951 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5952 if(pawnRow < 1) pawnRow = 1;
5953 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5955 /* User pieceToChar list overrules defaults */
5956 if(appData.pieceToCharTable != NULL)
5957 SetCharTable(pieceToChar, appData.pieceToCharTable);
5959 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5961 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5962 s = (ChessSquare) 0; /* account holding counts in guard band */
5963 for( i=0; i<BOARD_HEIGHT; i++ )
5964 initialPosition[i][j] = s;
5966 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5967 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5968 initialPosition[pawnRow][j] = WhitePawn;
5969 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5970 if(gameInfo.variant == VariantXiangqi) {
5972 initialPosition[pawnRow][j] =
5973 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5974 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5975 initialPosition[2][j] = WhiteCannon;
5976 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5980 if(gameInfo.variant == VariantGrand) {
5981 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5982 initialPosition[0][j] = WhiteRook;
5983 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5986 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5988 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5991 initialPosition[1][j] = WhiteBishop;
5992 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5994 initialPosition[1][j] = WhiteRook;
5995 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5998 if( nrCastlingRights == -1) {
5999 /* [HGM] Build normal castling rights (must be done after board sizing!) */
6000 /* This sets default castling rights from none to normal corners */
6001 /* Variants with other castling rights must set them themselves above */
6002 nrCastlingRights = 6;
6004 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6005 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6006 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6007 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6008 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6009 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6012 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6013 if(gameInfo.variant == VariantGreat) { // promotion commoners
6014 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6015 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6016 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6017 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6019 if( gameInfo.variant == VariantSChess ) {
6020 initialPosition[1][0] = BlackMarshall;
6021 initialPosition[2][0] = BlackAngel;
6022 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6023 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6024 initialPosition[1][1] = initialPosition[2][1] =
6025 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6027 if (appData.debugMode) {
6028 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6030 if(shuffleOpenings) {
6031 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6032 startedFromSetupPosition = TRUE;
6034 if(startedFromPositionFile) {
6035 /* [HGM] loadPos: use PositionFile for every new game */
6036 CopyBoard(initialPosition, filePosition);
6037 for(i=0; i<nrCastlingRights; i++)
6038 initialRights[i] = filePosition[CASTLING][i];
6039 startedFromSetupPosition = TRUE;
6042 CopyBoard(boards[0], initialPosition);
6044 if(oldx != gameInfo.boardWidth ||
6045 oldy != gameInfo.boardHeight ||
6046 oldv != gameInfo.variant ||
6047 oldh != gameInfo.holdingsWidth
6049 InitDrawingSizes(-2 ,0);
6051 oldv = gameInfo.variant;
6053 DrawPosition(TRUE, boards[currentMove]);
6057 SendBoard (ChessProgramState *cps, int moveNum)
6059 char message[MSG_SIZ];
6061 if (cps->useSetboard) {
6062 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6063 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6064 SendToProgram(message, cps);
6069 int i, j, left=0, right=BOARD_WIDTH;
6070 /* Kludge to set black to move, avoiding the troublesome and now
6071 * deprecated "black" command.
6073 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6074 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6076 if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6078 SendToProgram("edit\n", cps);
6079 SendToProgram("#\n", cps);
6080 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6081 bp = &boards[moveNum][i][left];
6082 for (j = left; j < right; j++, bp++) {
6083 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6084 if ((int) *bp < (int) BlackPawn) {
6085 if(j == BOARD_RGHT+1)
6086 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6087 else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6088 if(message[0] == '+' || message[0] == '~') {
6089 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6090 PieceToChar((ChessSquare)(DEMOTED *bp)),
6093 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6094 message[1] = BOARD_RGHT - 1 - j + '1';
6095 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6097 SendToProgram(message, cps);
6102 SendToProgram("c\n", cps);
6103 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6104 bp = &boards[moveNum][i][left];
6105 for (j = left; j < right; j++, bp++) {
6106 if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6107 if (((int) *bp != (int) EmptySquare)
6108 && ((int) *bp >= (int) BlackPawn)) {
6109 if(j == BOARD_LEFT-2)
6110 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6111 else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6113 if(message[0] == '+' || message[0] == '~') {
6114 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6115 PieceToChar((ChessSquare)(DEMOTED *bp)),
6118 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6119 message[1] = BOARD_RGHT - 1 - j + '1';
6120 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6122 SendToProgram(message, cps);
6127 SendToProgram(".\n", cps);
6129 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6132 char exclusionHeader[MSG_SIZ];
6133 int exCnt, excludePtr;
6134 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6135 static Exclusion excluTab[200];
6136 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6142 for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6143 exclusionHeader[19] = s ? '-' : '+'; // update tail state
6149 safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ);
6150 excludePtr = 24; exCnt = 0;
6155 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6156 { // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6157 char buf[2*MOVE_LEN], *p;
6158 Exclusion *e = excluTab;
6160 for(i=0; i<exCnt; i++)
6161 if(e[i].ff == fromX && e[i].fr == fromY &&
6162 e[i].tf == toX && e[i].tr == toY && e[i].pc == promoChar) break;
6163 if(i == exCnt) { // was not in exclude list; add it
6164 CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6165 if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6166 if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6169 e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6170 excludePtr++; e[i].mark = excludePtr++;
6171 for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6174 exclusionHeader[e[i].mark] = state;
6178 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6179 { // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6183 if(promoChar == -1) { // kludge to indicate best move
6184 if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6185 return 1; // if unparsable, abort
6187 // update exclusion map (resolving toggle by consulting existing state)
6188 k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6190 if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6191 if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6192 excludeMap[k] |= 1<<j;
6193 else excludeMap[k] &= ~(1<<j);
6195 UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6197 snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6198 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6199 SendToProgram(buf, &first);
6200 return (state == '+');
6204 ExcludeClick (int index)
6207 Exclusion *e = excluTab;
6208 if(index < 25) { // none, best or tail clicked
6209 if(index < 13) { // none: include all
6210 WriteMap(0); // clear map
6211 for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6212 SendToProgram("include all\n", &first); // and inform engine
6213 } else if(index > 18) { // tail
6214 if(exclusionHeader[19] == '-') { // tail was excluded
6215 SendToProgram("include all\n", &first);
6216 WriteMap(0); // clear map completely
6217 // now re-exclude selected moves
6218 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6219 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6220 } else { // tail was included or in mixed state
6221 SendToProgram("exclude all\n", &first);
6222 WriteMap(0xFF); // fill map completely
6223 // now re-include selected moves
6224 j = 0; // count them
6225 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6226 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6227 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6230 ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6233 for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6234 char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6235 ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6242 DefaultPromoChoice (int white)
6245 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6246 result = WhiteFerz; // no choice
6247 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6248 result= WhiteKing; // in Suicide Q is the last thing we want
6249 else if(gameInfo.variant == VariantSpartan)
6250 result = white ? WhiteQueen : WhiteAngel;
6251 else result = WhiteQueen;
6252 if(!white) result = WHITE_TO_BLACK result;
6256 static int autoQueen; // [HGM] oneclick
6259 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6261 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6262 /* [HGM] add Shogi promotions */
6263 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6268 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6269 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6271 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6272 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6275 piece = boards[currentMove][fromY][fromX];
6276 if(gameInfo.variant == VariantShogi) {
6277 promotionZoneSize = BOARD_HEIGHT/3;
6278 highestPromotingPiece = (int)WhiteFerz;
6279 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6280 promotionZoneSize = 3;
6283 // Treat Lance as Pawn when it is not representing Amazon
6284 if(gameInfo.variant != VariantSuper) {
6285 if(piece == WhiteLance) piece = WhitePawn; else
6286 if(piece == BlackLance) piece = BlackPawn;
6289 // next weed out all moves that do not touch the promotion zone at all
6290 if((int)piece >= BlackPawn) {
6291 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6293 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6295 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6296 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6299 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6301 // weed out mandatory Shogi promotions
6302 if(gameInfo.variant == VariantShogi) {
6303 if(piece >= BlackPawn) {
6304 if(toY == 0 && piece == BlackPawn ||
6305 toY == 0 && piece == BlackQueen ||
6306 toY <= 1 && piece == BlackKnight) {
6311 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6312 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6313 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6320 // weed out obviously illegal Pawn moves
6321 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6322 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6323 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6324 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6325 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6326 // note we are not allowed to test for valid (non-)capture, due to premove
6329 // we either have a choice what to promote to, or (in Shogi) whether to promote
6330 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6331 *promoChoice = PieceToChar(BlackFerz); // no choice
6334 // no sense asking what we must promote to if it is going to explode...
6335 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6336 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6339 // give caller the default choice even if we will not make it
6340 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6341 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6342 if( sweepSelect && gameInfo.variant != VariantGreat
6343 && gameInfo.variant != VariantGrand
6344 && gameInfo.variant != VariantSuper) return FALSE;
6345 if(autoQueen) return FALSE; // predetermined
6347 // suppress promotion popup on illegal moves that are not premoves
6348 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6349 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6350 if(appData.testLegality && !premove) {
6351 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6352 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6353 if(moveType != WhitePromotion && moveType != BlackPromotion)
6361 InPalace (int row, int column)
6362 { /* [HGM] for Xiangqi */
6363 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6364 column < (BOARD_WIDTH + 4)/2 &&
6365 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6370 PieceForSquare (int x, int y)
6372 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6375 return boards[currentMove][y][x];
6379 OKToStartUserMove (int x, int y)
6381 ChessSquare from_piece;
6384 if (matchMode) return FALSE;
6385 if (gameMode == EditPosition) return TRUE;
6387 if (x >= 0 && y >= 0)
6388 from_piece = boards[currentMove][y][x];
6390 from_piece = EmptySquare;
6392 if (from_piece == EmptySquare) return FALSE;
6394 white_piece = (int)from_piece >= (int)WhitePawn &&
6395 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6399 case TwoMachinesPlay:
6407 case MachinePlaysWhite:
6408 case IcsPlayingBlack:
6409 if (appData.zippyPlay) return FALSE;
6411 DisplayMoveError(_("You are playing Black"));
6416 case MachinePlaysBlack:
6417 case IcsPlayingWhite:
6418 if (appData.zippyPlay) return FALSE;
6420 DisplayMoveError(_("You are playing White"));
6425 case PlayFromGameFile:
6426 if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6428 if (!white_piece && WhiteOnMove(currentMove)) {
6429 DisplayMoveError(_("It is White's turn"));
6432 if (white_piece && !WhiteOnMove(currentMove)) {
6433 DisplayMoveError(_("It is Black's turn"));
6436 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6437 /* Editing correspondence game history */
6438 /* Could disallow this or prompt for confirmation */
6443 case BeginningOfGame:
6444 if (appData.icsActive) return FALSE;
6445 if (!appData.noChessProgram) {
6447 DisplayMoveError(_("You are playing White"));
6454 if (!white_piece && WhiteOnMove(currentMove)) {
6455 DisplayMoveError(_("It is White's turn"));
6458 if (white_piece && !WhiteOnMove(currentMove)) {
6459 DisplayMoveError(_("It is Black's turn"));
6468 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6469 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6470 && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6471 && gameMode != AnalyzeFile && gameMode != Training) {
6472 DisplayMoveError(_("Displayed position is not current"));
6479 OnlyMove (int *x, int *y, Boolean captures)
6481 DisambiguateClosure cl;
6482 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6484 case MachinePlaysBlack:
6485 case IcsPlayingWhite:
6486 case BeginningOfGame:
6487 if(!WhiteOnMove(currentMove)) return FALSE;
6489 case MachinePlaysWhite:
6490 case IcsPlayingBlack:
6491 if(WhiteOnMove(currentMove)) return FALSE;
6498 cl.pieceIn = EmptySquare;
6503 cl.promoCharIn = NULLCHAR;
6504 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6505 if( cl.kind == NormalMove ||
6506 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6507 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6508 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6515 if(cl.kind != ImpossibleMove) return FALSE;
6516 cl.pieceIn = EmptySquare;
6521 cl.promoCharIn = NULLCHAR;
6522 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6523 if( cl.kind == NormalMove ||
6524 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6525 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6526 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6531 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6537 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6538 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6539 int lastLoadGameUseList = FALSE;
6540 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6541 ChessMove lastLoadGameStart = EndOfFile;
6545 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6548 ChessSquare pdown, pup;
6549 int ff=fromX, rf=fromY, ft=toX, rt=toY;
6552 /* Check if the user is playing in turn. This is complicated because we
6553 let the user "pick up" a piece before it is his turn. So the piece he
6554 tried to pick up may have been captured by the time he puts it down!
6555 Therefore we use the color the user is supposed to be playing in this
6556 test, not the color of the piece that is currently on the starting
6557 square---except in EditGame mode, where the user is playing both
6558 sides; fortunately there the capture race can't happen. (It can
6559 now happen in IcsExamining mode, but that's just too bad. The user
6560 will get a somewhat confusing message in that case.)
6565 case TwoMachinesPlay:
6569 /* We switched into a game mode where moves are not accepted,
6570 perhaps while the mouse button was down. */
6573 case MachinePlaysWhite:
6574 /* User is moving for Black */
6575 if (WhiteOnMove(currentMove)) {
6576 DisplayMoveError(_("It is White's turn"));
6581 case MachinePlaysBlack:
6582 /* User is moving for White */
6583 if (!WhiteOnMove(currentMove)) {
6584 DisplayMoveError(_("It is Black's turn"));
6589 case PlayFromGameFile:
6590 if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6593 case BeginningOfGame:
6596 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6597 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6598 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6599 /* User is moving for Black */
6600 if (WhiteOnMove(currentMove)) {
6601 DisplayMoveError(_("It is White's turn"));
6605 /* User is moving for White */
6606 if (!WhiteOnMove(currentMove)) {
6607 DisplayMoveError(_("It is Black's turn"));
6613 case IcsPlayingBlack:
6614 /* User is moving for Black */
6615 if (WhiteOnMove(currentMove)) {
6616 if (!appData.premove) {
6617 DisplayMoveError(_("It is White's turn"));
6618 } else if (toX >= 0 && toY >= 0) {
6621 premoveFromX = fromX;
6622 premoveFromY = fromY;
6623 premovePromoChar = promoChar;
6625 if (appData.debugMode)
6626 fprintf(debugFP, "Got premove: fromX %d,"
6627 "fromY %d, toX %d, toY %d\n",
6628 fromX, fromY, toX, toY);
6634 case IcsPlayingWhite:
6635 /* User is moving for White */
6636 if (!WhiteOnMove(currentMove)) {
6637 if (!appData.premove) {
6638 DisplayMoveError(_("It is Black's turn"));
6639 } else if (toX >= 0 && toY >= 0) {
6642 premoveFromX = fromX;
6643 premoveFromY = fromY;
6644 premovePromoChar = promoChar;
6646 if (appData.debugMode)
6647 fprintf(debugFP, "Got premove: fromX %d,"
6648 "fromY %d, toX %d, toY %d\n",
6649 fromX, fromY, toX, toY);
6659 /* EditPosition, empty square, or different color piece;
6660 click-click move is possible */
6661 if (toX == -2 || toY == -2) {
6662 boards[0][fromY][fromX] = EmptySquare;
6663 DrawPosition(FALSE, boards[currentMove]);
6665 } else if (toX >= 0 && toY >= 0) {
6666 boards[0][toY][toX] = boards[0][fromY][fromX];
6667 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6668 if(boards[0][fromY][0] != EmptySquare) {
6669 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6670 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6673 if(fromX == BOARD_RGHT+1) {
6674 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6675 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6676 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6679 boards[0][fromY][fromX] = gatingPiece;
6680 DrawPosition(FALSE, boards[currentMove]);
6686 if(toX < 0 || toY < 0) return;
6687 pdown = boards[currentMove][fromY][fromX];
6688 pup = boards[currentMove][toY][toX];
6690 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6691 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6692 if( pup != EmptySquare ) return;
6693 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6694 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6695 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6696 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6697 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6698 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6699 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6703 /* [HGM] always test for legality, to get promotion info */
6704 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6705 fromY, fromX, toY, toX, promoChar);
6707 if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6709 /* [HGM] but possibly ignore an IllegalMove result */
6710 if (appData.testLegality) {
6711 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6712 DisplayMoveError(_("Illegal move"));
6717 if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6718 if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6719 ClearPremoveHighlights(); // was included
6720 else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights
6724 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6727 /* Common tail of UserMoveEvent and DropMenuEvent */
6729 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6733 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6734 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6735 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6736 if(WhiteOnMove(currentMove)) {
6737 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6739 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6743 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6744 move type in caller when we know the move is a legal promotion */
6745 if(moveType == NormalMove && promoChar)
6746 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6748 /* [HGM] <popupFix> The following if has been moved here from
6749 UserMoveEvent(). Because it seemed to belong here (why not allow
6750 piece drops in training games?), and because it can only be
6751 performed after it is known to what we promote. */
6752 if (gameMode == Training) {
6753 /* compare the move played on the board to the next move in the
6754 * game. If they match, display the move and the opponent's response.
6755 * If they don't match, display an error message.
6759 CopyBoard(testBoard, boards[currentMove]);
6760 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6762 if (CompareBoards(testBoard, boards[currentMove+1])) {
6763 ForwardInner(currentMove+1);
6765 /* Autoplay the opponent's response.
6766 * if appData.animate was TRUE when Training mode was entered,
6767 * the response will be animated.
6769 saveAnimate = appData.animate;
6770 appData.animate = animateTraining;
6771 ForwardInner(currentMove+1);
6772 appData.animate = saveAnimate;
6774 /* check for the end of the game */
6775 if (currentMove >= forwardMostMove) {
6776 gameMode = PlayFromGameFile;
6778 SetTrainingModeOff();
6779 DisplayInformation(_("End of game"));
6782 DisplayError(_("Incorrect move"), 0);
6787 /* Ok, now we know that the move is good, so we can kill
6788 the previous line in Analysis Mode */
6789 if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6790 && currentMove < forwardMostMove) {
6791 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6792 else forwardMostMove = currentMove;
6797 /* If we need the chess program but it's dead, restart it */
6798 ResurrectChessProgram();
6800 /* A user move restarts a paused game*/
6804 thinkOutput[0] = NULLCHAR;
6806 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6808 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6809 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6813 if (gameMode == BeginningOfGame) {
6814 if (appData.noChessProgram) {
6815 gameMode = EditGame;
6819 gameMode = MachinePlaysBlack;
6822 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6824 if (first.sendName) {
6825 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6826 SendToProgram(buf, &first);
6833 /* Relay move to ICS or chess engine */
6834 if (appData.icsActive) {
6835 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6836 gameMode == IcsExamining) {
6837 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6838 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6840 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6842 // also send plain move, in case ICS does not understand atomic claims
6843 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6847 if (first.sendTime && (gameMode == BeginningOfGame ||
6848 gameMode == MachinePlaysWhite ||
6849 gameMode == MachinePlaysBlack)) {
6850 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6852 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6853 // [HGM] book: if program might be playing, let it use book
6854 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6855 first.maybeThinking = TRUE;
6856 } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6857 if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6858 SendBoard(&first, currentMove+1);
6859 } else SendMoveToProgram(forwardMostMove-1, &first);
6860 if (currentMove == cmailOldMove + 1) {
6861 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6865 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6869 if(appData.testLegality)
6870 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6876 if (WhiteOnMove(currentMove)) {
6877 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6879 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6883 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6888 case MachinePlaysBlack:
6889 case MachinePlaysWhite:
6890 /* disable certain menu options while machine is thinking */
6891 SetMachineThinkingEnables();
6898 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6899 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6901 if(bookHit) { // [HGM] book: simulate book reply
6902 static char bookMove[MSG_SIZ]; // a bit generous?
6904 programStats.nodes = programStats.depth = programStats.time =
6905 programStats.score = programStats.got_only_move = 0;
6906 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6908 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6909 strcat(bookMove, bookHit);
6910 HandleMachineMove(bookMove, &first);
6916 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6918 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6919 Markers *m = (Markers *) closure;
6920 if(rf == fromY && ff == fromX)
6921 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6922 || kind == WhiteCapturesEnPassant
6923 || kind == BlackCapturesEnPassant);
6924 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6928 MarkTargetSquares (int clear)
6931 if(clear) // no reason to ever suppress clearing
6932 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6933 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6934 !appData.testLegality || gameMode == EditPosition) return;
6937 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6938 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6939 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6941 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6944 DrawPosition(FALSE, NULL);
6948 Explode (Board board, int fromX, int fromY, int toX, int toY)
6950 if(gameInfo.variant == VariantAtomic &&
6951 (board[toY][toX] != EmptySquare || // capture?
6952 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6953 board[fromY][fromX] == BlackPawn )
6955 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6961 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6964 CanPromote (ChessSquare piece, int y)
6966 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6967 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6968 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6969 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6970 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6971 gameInfo.variant == VariantMakruk) return FALSE;
6972 return (piece == BlackPawn && y == 1 ||
6973 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6974 piece == BlackLance && y == 1 ||
6975 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6979 LeftClick (ClickType clickType, int xPix, int yPix)
6982 Boolean saveAnimate;
6983 static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
6984 char promoChoice = NULLCHAR;
6986 static TimeMark lastClickTime, prevClickTime;
6988 if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6990 prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6992 if (clickType == Press) ErrorPopDown();
6994 x = EventToSquare(xPix, BOARD_WIDTH);
6995 y = EventToSquare(yPix, BOARD_HEIGHT);
6996 if (!flipView && y >= 0) {
6997 y = BOARD_HEIGHT - 1 - y;
6999 if (flipView && x >= 0) {
7000 x = BOARD_WIDTH - 1 - x;
7003 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7004 defaultPromoChoice = promoSweep;
7005 promoSweep = EmptySquare; // terminate sweep
7006 promoDefaultAltered = TRUE;
7007 if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7010 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7011 if(clickType == Release) return; // ignore upclick of click-click destination
7012 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7013 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7014 if(gameInfo.holdingsWidth &&
7015 (WhiteOnMove(currentMove)
7016 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7017 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7018 // click in right holdings, for determining promotion piece
7019 ChessSquare p = boards[currentMove][y][x];
7020 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7021 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7022 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7023 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7028 DrawPosition(FALSE, boards[currentMove]);
7032 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7033 if(clickType == Press
7034 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7035 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7036 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7039 if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7040 // could be static click on premove from-square: abort premove
7042 ClearPremoveHighlights();
7045 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7046 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7048 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7049 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7050 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7051 defaultPromoChoice = DefaultPromoChoice(side);
7054 autoQueen = appData.alwaysPromoteToQueen;
7058 gatingPiece = EmptySquare;
7059 if (clickType != Press) {
7060 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7061 DragPieceEnd(xPix, yPix); dragging = 0;
7062 DrawPosition(FALSE, NULL);
7066 doubleClick = FALSE;
7067 fromX = x; fromY = y; toX = toY = -1;
7068 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7069 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7070 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7072 if (OKToStartUserMove(fromX, fromY)) {
7074 MarkTargetSquares(0);
7075 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7076 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7077 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7078 promoSweep = defaultPromoChoice;
7079 selectFlag = 0; lastX = xPix; lastY = yPix;
7080 Sweep(0); // Pawn that is going to promote: preview promotion piece
7081 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7083 if (appData.highlightDragging) {
7084 SetHighlights(fromX, fromY, -1, -1);
7088 } else fromX = fromY = -1;
7094 if (clickType == Press && gameMode != EditPosition) {
7099 // ignore off-board to clicks
7100 if(y < 0 || x < 0) return;
7102 /* Check if clicking again on the same color piece */
7103 fromP = boards[currentMove][fromY][fromX];
7104 toP = boards[currentMove][y][x];
7105 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7106 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7107 WhitePawn <= toP && toP <= WhiteKing &&
7108 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7109 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7110 (BlackPawn <= fromP && fromP <= BlackKing &&
7111 BlackPawn <= toP && toP <= BlackKing &&
7112 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7113 !(fromP == BlackKing && toP == BlackRook && frc))) {
7114 /* Clicked again on same color piece -- changed his mind */
7115 second = (x == fromX && y == fromY);
7116 if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7117 second = FALSE; // first double-click rather than scond click
7118 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7120 promoDefaultAltered = FALSE;
7121 MarkTargetSquares(1);
7122 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7123 if (appData.highlightDragging) {
7124 SetHighlights(x, y, -1, -1);
7128 if (OKToStartUserMove(x, y)) {
7129 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7130 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7131 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7132 gatingPiece = boards[currentMove][fromY][fromX];
7133 else gatingPiece = doubleClick ? fromP : EmptySquare;
7135 fromY = y; dragging = 1;
7136 MarkTargetSquares(0);
7137 DragPieceBegin(xPix, yPix, FALSE);
7138 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7139 promoSweep = defaultPromoChoice;
7140 selectFlag = 0; lastX = xPix; lastY = yPix;
7141 Sweep(0); // Pawn that is going to promote: preview promotion piece
7145 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7148 // ignore clicks on holdings
7149 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7152 if (clickType == Release && x == fromX && y == fromY) {
7153 DragPieceEnd(xPix, yPix); dragging = 0;
7155 // a deferred attempt to click-click move an empty square on top of a piece
7156 boards[currentMove][y][x] = EmptySquare;
7158 DrawPosition(FALSE, boards[currentMove]);
7159 fromX = fromY = -1; clearFlag = 0;
7162 if (appData.animateDragging) {
7163 /* Undo animation damage if any */
7164 DrawPosition(FALSE, NULL);
7166 if (second || sweepSelecting) {
7167 /* Second up/down in same square; just abort move */
7168 if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7169 second = sweepSelecting = 0;
7171 gatingPiece = EmptySquare;
7174 ClearPremoveHighlights();
7176 /* First upclick in same square; start click-click mode */
7177 SetHighlights(x, y, -1, -1);
7184 /* we now have a different from- and (possibly off-board) to-square */
7185 /* Completed move */
7186 if(!sweepSelecting) {
7189 saveAnimate = appData.animate;
7190 } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7192 if (clickType == Press) {
7193 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7194 // must be Edit Position mode with empty-square selected
7195 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7196 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7199 if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7200 if(appData.sweepSelect) {
7201 ChessSquare piece = boards[currentMove][fromY][fromX];
7202 promoSweep = defaultPromoChoice;
7203 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7204 selectFlag = 0; lastX = xPix; lastY = yPix;
7205 Sweep(0); // Pawn that is going to promote: preview promotion piece
7207 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7208 MarkTargetSquares(1);
7210 return; // promo popup appears on up-click
7212 /* Finish clickclick move */
7213 if (appData.animate || appData.highlightLastMove) {
7214 SetHighlights(fromX, fromY, toX, toY);
7219 /* Finish drag move */
7220 if (appData.highlightLastMove) {
7221 SetHighlights(fromX, fromY, toX, toY);
7225 DragPieceEnd(xPix, yPix); dragging = 0;
7226 /* Don't animate move and drag both */
7227 appData.animate = FALSE;
7229 MarkTargetSquares(1);
7231 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7232 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7233 ChessSquare piece = boards[currentMove][fromY][fromX];
7234 if(gameMode == EditPosition && piece != EmptySquare &&
7235 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7238 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7239 n = PieceToNumber(piece - (int)BlackPawn);
7240 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7241 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7242 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7244 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7245 n = PieceToNumber(piece);
7246 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7247 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7248 boards[currentMove][n][BOARD_WIDTH-2]++;
7250 boards[currentMove][fromY][fromX] = EmptySquare;
7254 DrawPosition(TRUE, boards[currentMove]);
7258 // off-board moves should not be highlighted
7259 if(x < 0 || y < 0) ClearHighlights();
7261 if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7263 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7264 SetHighlights(fromX, fromY, toX, toY);
7265 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7266 // [HGM] super: promotion to captured piece selected from holdings
7267 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7268 promotionChoice = TRUE;
7269 // kludge follows to temporarily execute move on display, without promoting yet
7270 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7271 boards[currentMove][toY][toX] = p;
7272 DrawPosition(FALSE, boards[currentMove]);
7273 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7274 boards[currentMove][toY][toX] = q;
7275 DisplayMessage("Click in holdings to choose piece", "");
7280 int oldMove = currentMove;
7281 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7282 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7283 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7284 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7285 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7286 DrawPosition(TRUE, boards[currentMove]);
7289 appData.animate = saveAnimate;
7290 if (appData.animate || appData.animateDragging) {
7291 /* Undo animation damage if needed */
7292 DrawPosition(FALSE, NULL);
7297 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7298 { // front-end-free part taken out of PieceMenuPopup
7299 int whichMenu; int xSqr, ySqr;
7301 if(seekGraphUp) { // [HGM] seekgraph
7302 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7303 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7307 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7308 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7309 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7310 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7311 if(action == Press) {
7312 originalFlip = flipView;
7313 flipView = !flipView; // temporarily flip board to see game from partners perspective
7314 DrawPosition(TRUE, partnerBoard);
7315 DisplayMessage(partnerStatus, "");
7317 } else if(action == Release) {
7318 flipView = originalFlip;
7319 DrawPosition(TRUE, boards[currentMove]);
7325 xSqr = EventToSquare(x, BOARD_WIDTH);
7326 ySqr = EventToSquare(y, BOARD_HEIGHT);
7327 if (action == Release) {
7328 if(pieceSweep != EmptySquare) {
7329 EditPositionMenuEvent(pieceSweep, toX, toY);
7330 pieceSweep = EmptySquare;
7331 } else UnLoadPV(); // [HGM] pv
7333 if (action != Press) return -2; // return code to be ignored
7336 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7338 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7339 if (xSqr < 0 || ySqr < 0) return -1;
7340 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7341 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7342 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7343 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7347 if(!appData.icsEngineAnalyze) return -1;
7348 case IcsPlayingWhite:
7349 case IcsPlayingBlack:
7350 if(!appData.zippyPlay) goto noZip;
7353 case MachinePlaysWhite:
7354 case MachinePlaysBlack:
7355 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7356 if (!appData.dropMenu) {
7358 return 2; // flag front-end to grab mouse events
7360 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7361 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7364 if (xSqr < 0 || ySqr < 0) return -1;
7365 if (!appData.dropMenu || appData.testLegality &&
7366 gameInfo.variant != VariantBughouse &&
7367 gameInfo.variant != VariantCrazyhouse) return -1;
7368 whichMenu = 1; // drop menu
7374 if (((*fromX = xSqr) < 0) ||
7375 ((*fromY = ySqr) < 0)) {
7376 *fromX = *fromY = -1;
7380 *fromX = BOARD_WIDTH - 1 - *fromX;
7382 *fromY = BOARD_HEIGHT - 1 - *fromY;
7388 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7390 // char * hint = lastHint;
7391 FrontEndProgramStats stats;
7393 stats.which = cps == &first ? 0 : 1;
7394 stats.depth = cpstats->depth;
7395 stats.nodes = cpstats->nodes;
7396 stats.score = cpstats->score;
7397 stats.time = cpstats->time;
7398 stats.pv = cpstats->movelist;
7399 stats.hint = lastHint;
7400 stats.an_move_index = 0;
7401 stats.an_move_count = 0;
7403 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7404 stats.hint = cpstats->move_name;
7405 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7406 stats.an_move_count = cpstats->nr_moves;
7409 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
7411 SetProgramStats( &stats );
7415 ClearEngineOutputPane (int which)
7417 static FrontEndProgramStats dummyStats;
7418 dummyStats.which = which;
7419 dummyStats.pv = "#";
7420 SetProgramStats( &dummyStats );
7423 #define MAXPLAYERS 500
7426 TourneyStandings (int display)
7428 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7429 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7430 char result, *p, *names[MAXPLAYERS];
7432 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7433 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7434 names[0] = p = strdup(appData.participants);
7435 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7437 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7439 while(result = appData.results[nr]) {
7440 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7441 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7442 wScore = bScore = 0;
7444 case '+': wScore = 2; break;
7445 case '-': bScore = 2; break;
7446 case '=': wScore = bScore = 1; break;
7448 case '*': return strdup("busy"); // tourney not finished
7456 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7457 for(w=0; w<nPlayers; w++) {
7459 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7460 ranking[w] = b; points[w] = bScore; score[b] = -2;
7462 p = malloc(nPlayers*34+1);
7463 for(w=0; w<nPlayers && w<display; w++)
7464 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7470 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7471 { // count all piece types
7473 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7474 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7475 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7478 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7479 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7480 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7481 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7482 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7483 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7488 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7490 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7491 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7493 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7494 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7495 if(myPawns == 2 && nMine == 3) // KPP
7496 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7497 if(myPawns == 1 && nMine == 2) // KP
7498 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7499 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7500 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7501 if(myPawns) return FALSE;
7502 if(pCnt[WhiteRook+side])
7503 return pCnt[BlackRook-side] ||
7504 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7505 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7506 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7507 if(pCnt[WhiteCannon+side]) {
7508 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7509 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7511 if(pCnt[WhiteKnight+side])
7512 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7517 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7519 VariantClass v = gameInfo.variant;
7521 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7522 if(v == VariantShatranj) return TRUE; // always winnable through baring
7523 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7524 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7526 if(v == VariantXiangqi) {
7527 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7529 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7530 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7531 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7532 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7533 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7534 if(stale) // we have at least one last-rank P plus perhaps C
7535 return majors // KPKX
7536 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7538 return pCnt[WhiteFerz+side] // KCAK
7539 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7540 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7541 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7543 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7544 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7546 if(nMine == 1) return FALSE; // bare King
7547 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
7548 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7549 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7550 // by now we have King + 1 piece (or multiple Bishops on the same color)
7551 if(pCnt[WhiteKnight+side])
7552 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7553 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7554 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7556 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7557 if(pCnt[WhiteAlfil+side])
7558 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7559 if(pCnt[WhiteWazir+side])
7560 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7567 CompareWithRights (Board b1, Board b2)
7570 if(!CompareBoards(b1, b2)) return FALSE;
7571 if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7572 /* compare castling rights */
7573 if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7574 rights++; /* King lost rights, while rook still had them */
7575 if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7576 if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7577 rights++; /* but at least one rook lost them */
7579 if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7581 if( b1[CASTLING][5] != NoRights ) {
7582 if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7589 Adjudicate (ChessProgramState *cps)
7590 { // [HGM] some adjudications useful with buggy engines
7591 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7592 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7593 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7594 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7595 int k, count = 0; static int bare = 1;
7596 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7597 Boolean canAdjudicate = !appData.icsActive;
7599 // most tests only when we understand the game, i.e. legality-checking on
7600 if( appData.testLegality )
7601 { /* [HGM] Some more adjudications for obstinate engines */
7602 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7603 static int moveCount = 6;
7605 char *reason = NULL;
7607 /* Count what is on board. */
7608 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7610 /* Some material-based adjudications that have to be made before stalemate test */
7611 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7612 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7613 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7614 if(canAdjudicate && appData.checkMates) {
7616 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7617 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7618 "Xboard adjudication: King destroyed", GE_XBOARD );
7623 /* Bare King in Shatranj (loses) or Losers (wins) */
7624 if( nrW == 1 || nrB == 1) {
7625 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7626 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7627 if(canAdjudicate && appData.checkMates) {
7629 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7630 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7631 "Xboard adjudication: Bare king", GE_XBOARD );
7635 if( gameInfo.variant == VariantShatranj && --bare < 0)
7637 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7638 if(canAdjudicate && appData.checkMates) {
7639 /* but only adjudicate if adjudication enabled */
7641 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7642 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7643 "Xboard adjudication: Bare king", GE_XBOARD );
7650 // don't wait for engine to announce game end if we can judge ourselves
7651 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7653 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7654 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7655 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7656 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7659 reason = "Xboard adjudication: 3rd check";
7660 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7670 reason = "Xboard adjudication: Stalemate";
7671 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7672 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7673 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7674 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7675 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7676 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7677 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7678 EP_CHECKMATE : EP_WINS);
7679 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7680 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7684 reason = "Xboard adjudication: Checkmate";
7685 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7689 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7691 result = GameIsDrawn; break;
7693 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7695 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7699 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7701 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7702 GameEnds( result, reason, GE_XBOARD );
7706 /* Next absolutely insufficient mating material. */
7707 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7708 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7709 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7711 /* always flag draws, for judging claims */
7712 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7714 if(canAdjudicate && appData.materialDraws) {
7715 /* but only adjudicate them if adjudication enabled */
7716 if(engineOpponent) {
7717 SendToProgram("force\n", engineOpponent); // suppress reply
7718 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7720 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7725 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7726 if(gameInfo.variant == VariantXiangqi ?
7727 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7729 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7730 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7731 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7732 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7734 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7735 { /* if the first 3 moves do not show a tactical win, declare draw */
7736 if(engineOpponent) {
7737 SendToProgram("force\n", engineOpponent); // suppress reply
7738 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7740 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7743 } else moveCount = 6;
7746 // Repetition draws and 50-move rule can be applied independently of legality testing
7748 /* Check for rep-draws */
7750 for(k = forwardMostMove-2;
7751 k>=backwardMostMove && k>=forwardMostMove-100 &&
7752 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7753 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7756 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7757 /* compare castling rights */
7758 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7759 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7760 rights++; /* King lost rights, while rook still had them */
7761 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7762 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7763 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7764 rights++; /* but at least one rook lost them */
7766 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7767 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7769 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7770 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7771 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7774 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7775 && appData.drawRepeats > 1) {
7776 /* adjudicate after user-specified nr of repeats */
7777 int result = GameIsDrawn;
7778 char *details = "XBoard adjudication: repetition draw";
7779 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7780 // [HGM] xiangqi: check for forbidden perpetuals
7781 int m, ourPerpetual = 1, hisPerpetual = 1;
7782 for(m=forwardMostMove; m>k; m-=2) {
7783 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7784 ourPerpetual = 0; // the current mover did not always check
7785 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7786 hisPerpetual = 0; // the opponent did not always check
7788 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7789 ourPerpetual, hisPerpetual);
7790 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7791 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7792 details = "Xboard adjudication: perpetual checking";
7794 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7795 break; // (or we would have caught him before). Abort repetition-checking loop.
7797 // Now check for perpetual chases
7798 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7799 hisPerpetual = PerpetualChase(k, forwardMostMove);
7800 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7801 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7802 static char resdet[MSG_SIZ];
7803 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7805 snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7807 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7808 break; // Abort repetition-checking loop.
7810 // if neither of us is checking or chasing all the time, or both are, it is draw
7812 if(engineOpponent) {
7813 SendToProgram("force\n", engineOpponent); // suppress reply
7814 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7816 GameEnds( result, details, GE_XBOARD );
7819 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7820 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7824 /* Now we test for 50-move draws. Determine ply count */
7825 count = forwardMostMove;
7826 /* look for last irreversble move */
7827 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7829 /* if we hit starting position, add initial plies */
7830 if( count == backwardMostMove )
7831 count -= initialRulePlies;
7832 count = forwardMostMove - count;
7833 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7834 // adjust reversible move counter for checks in Xiangqi
7835 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7836 if(i < backwardMostMove) i = backwardMostMove;
7837 while(i <= forwardMostMove) {
7838 lastCheck = inCheck; // check evasion does not count
7839 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7840 if(inCheck || lastCheck) count--; // check does not count
7845 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7846 /* this is used to judge if draw claims are legal */
7847 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7848 if(engineOpponent) {
7849 SendToProgram("force\n", engineOpponent); // suppress reply
7850 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7852 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7856 /* if draw offer is pending, treat it as a draw claim
7857 * when draw condition present, to allow engines a way to
7858 * claim draws before making their move to avoid a race
7859 * condition occurring after their move
7861 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7863 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7864 p = "Draw claim: 50-move rule";
7865 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7866 p = "Draw claim: 3-fold repetition";
7867 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7868 p = "Draw claim: insufficient mating material";
7869 if( p != NULL && canAdjudicate) {
7870 if(engineOpponent) {
7871 SendToProgram("force\n", engineOpponent); // suppress reply
7872 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7874 GameEnds( GameIsDrawn, p, GE_XBOARD );
7879 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7880 if(engineOpponent) {
7881 SendToProgram("force\n", engineOpponent); // suppress reply
7882 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7884 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7891 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7892 { // [HGM] book: this routine intercepts moves to simulate book replies
7893 char *bookHit = NULL;
7895 //first determine if the incoming move brings opponent into his book
7896 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7897 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7898 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7899 if(bookHit != NULL && !cps->bookSuspend) {
7900 // make sure opponent is not going to reply after receiving move to book position
7901 SendToProgram("force\n", cps);
7902 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7904 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7905 // now arrange restart after book miss
7907 // after a book hit we never send 'go', and the code after the call to this routine
7908 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7909 char buf[MSG_SIZ], *move = bookHit;
7911 int fromX, fromY, toX, toY;
7915 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7916 &fromX, &fromY, &toX, &toY, &promoChar)) {
7917 (void) CoordsToAlgebraic(boards[forwardMostMove],
7918 PosFlags(forwardMostMove),
7919 fromY, fromX, toY, toX, promoChar, move);
7921 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7925 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7926 SendToProgram(buf, cps);
7927 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7928 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7929 SendToProgram("go\n", cps);
7930 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7931 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7932 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7933 SendToProgram("go\n", cps);
7934 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7936 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7940 LoadError (char *errmess, ChessProgramState *cps)
7941 { // unloads engine and switches back to -ncp mode if it was first
7942 if(cps->initDone) return FALSE;
7943 cps->isr = NULL; // this should suppress further error popups from breaking pipes
7944 DestroyChildProcess(cps->pr, 9 ); // just to be sure
7947 appData.noChessProgram = TRUE;
7948 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7949 gameMode = BeginningOfGame; ModeHighlight();
7952 if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7953 DisplayMessage("", ""); // erase waiting message
7954 if(errmess) DisplayError(errmess, 0); // announce reason, if given
7959 ChessProgramState *savedState;
7961 DeferredBookMove (void)
7963 if(savedState->lastPing != savedState->lastPong)
7964 ScheduleDelayedEvent(DeferredBookMove, 10);
7966 HandleMachineMove(savedMessage, savedState);
7969 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7972 HandleMachineMove (char *message, ChessProgramState *cps)
7974 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7975 char realname[MSG_SIZ];
7976 int fromX, fromY, toX, toY;
7980 int machineWhite, oldError;
7983 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7984 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7985 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7986 DisplayError(_("Invalid pairing from pairing engine"), 0);
7989 pairingReceived = 1;
7991 return; // Skim the pairing messages here.
7994 oldError = cps->userError; cps->userError = 0;
7996 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7998 * Kludge to ignore BEL characters
8000 while (*message == '\007') message++;
8003 * [HGM] engine debug message: ignore lines starting with '#' character
8005 if(cps->debug && *message == '#') return;
8008 * Look for book output
8010 if (cps == &first && bookRequested) {
8011 if (message[0] == '\t' || message[0] == ' ') {
8012 /* Part of the book output is here; append it */
8013 strcat(bookOutput, message);
8014 strcat(bookOutput, " \n");
8016 } else if (bookOutput[0] != NULLCHAR) {
8017 /* All of book output has arrived; display it */
8018 char *p = bookOutput;
8019 while (*p != NULLCHAR) {
8020 if (*p == '\t') *p = ' ';
8023 DisplayInformation(bookOutput);
8024 bookRequested = FALSE;
8025 /* Fall through to parse the current output */
8030 * Look for machine move.
8032 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8033 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8035 /* This method is only useful on engines that support ping */
8036 if (cps->lastPing != cps->lastPong) {
8037 if (gameMode == BeginningOfGame) {
8038 /* Extra move from before last new; ignore */
8039 if (appData.debugMode) {
8040 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8043 if (appData.debugMode) {
8044 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8045 cps->which, gameMode);
8048 SendToProgram("undo\n", cps);
8054 case BeginningOfGame:
8055 /* Extra move from before last reset; ignore */
8056 if (appData.debugMode) {
8057 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8064 /* Extra move after we tried to stop. The mode test is
8065 not a reliable way of detecting this problem, but it's
8066 the best we can do on engines that don't support ping.
8068 if (appData.debugMode) {
8069 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8070 cps->which, gameMode);
8072 SendToProgram("undo\n", cps);
8075 case MachinePlaysWhite:
8076 case IcsPlayingWhite:
8077 machineWhite = TRUE;
8080 case MachinePlaysBlack:
8081 case IcsPlayingBlack:
8082 machineWhite = FALSE;
8085 case TwoMachinesPlay:
8086 machineWhite = (cps->twoMachinesColor[0] == 'w');
8089 if (WhiteOnMove(forwardMostMove) != machineWhite) {
8090 if (appData.debugMode) {
8092 "Ignoring move out of turn by %s, gameMode %d"
8093 ", forwardMost %d\n",
8094 cps->which, gameMode, forwardMostMove);
8099 if(cps->alphaRank) AlphaRank(machineMove, 4);
8100 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8101 &fromX, &fromY, &toX, &toY, &promoChar)) {
8102 /* Machine move could not be parsed; ignore it. */
8103 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8104 machineMove, _(cps->which));
8105 DisplayError(buf1, 0);
8106 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8107 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8108 if (gameMode == TwoMachinesPlay) {
8109 GameEnds(machineWhite ? BlackWins : WhiteWins,
8115 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8116 /* So we have to redo legality test with true e.p. status here, */
8117 /* to make sure an illegal e.p. capture does not slip through, */
8118 /* to cause a forfeit on a justified illegal-move complaint */
8119 /* of the opponent. */
8120 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8122 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8123 fromY, fromX, toY, toX, promoChar);
8124 if(moveType == IllegalMove) {
8125 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8126 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8127 GameEnds(machineWhite ? BlackWins : WhiteWins,
8130 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8131 /* [HGM] Kludge to handle engines that send FRC-style castling
8132 when they shouldn't (like TSCP-Gothic) */
8134 case WhiteASideCastleFR:
8135 case BlackASideCastleFR:
8137 currentMoveString[2]++;
8139 case WhiteHSideCastleFR:
8140 case BlackHSideCastleFR:
8142 currentMoveString[2]--;
8144 default: ; // nothing to do, but suppresses warning of pedantic compilers
8147 hintRequested = FALSE;
8148 lastHint[0] = NULLCHAR;
8149 bookRequested = FALSE;
8150 /* Program may be pondering now */
8151 cps->maybeThinking = TRUE;
8152 if (cps->sendTime == 2) cps->sendTime = 1;
8153 if (cps->offeredDraw) cps->offeredDraw--;
8155 /* [AS] Save move info*/
8156 pvInfoList[ forwardMostMove ].score = programStats.score;
8157 pvInfoList[ forwardMostMove ].depth = programStats.depth;
8158 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
8160 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8162 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8163 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8166 while( count < adjudicateLossPlies ) {
8167 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8170 score = -score; /* Flip score for winning side */
8173 if( score > adjudicateLossThreshold ) {
8180 if( count >= adjudicateLossPlies ) {
8181 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8183 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8184 "Xboard adjudication",
8191 if(Adjudicate(cps)) {
8192 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8193 return; // [HGM] adjudicate: for all automatic game ends
8197 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8199 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8200 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8202 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8204 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8206 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8207 char buf[3*MSG_SIZ];
8209 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8210 programStats.score / 100.,
8212 programStats.time / 100.,
8213 (unsigned int)programStats.nodes,
8214 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8215 programStats.movelist);
8217 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8222 /* [AS] Clear stats for next move */
8223 ClearProgramStats();
8224 thinkOutput[0] = NULLCHAR;
8225 hiddenThinkOutputState = 0;
8228 if (gameMode == TwoMachinesPlay) {
8229 /* [HGM] relaying draw offers moved to after reception of move */
8230 /* and interpreting offer as claim if it brings draw condition */
8231 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8232 SendToProgram("draw\n", cps->other);
8234 if (cps->other->sendTime) {
8235 SendTimeRemaining(cps->other,
8236 cps->other->twoMachinesColor[0] == 'w');
8238 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8239 if (firstMove && !bookHit) {
8241 if (cps->other->useColors) {
8242 SendToProgram(cps->other->twoMachinesColor, cps->other);
8244 SendToProgram("go\n", cps->other);
8246 cps->other->maybeThinking = TRUE;
8249 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8251 if (!pausing && appData.ringBellAfterMoves) {
8256 * Reenable menu items that were disabled while
8257 * machine was thinking
8259 if (gameMode != TwoMachinesPlay)
8260 SetUserThinkingEnables();
8262 // [HGM] book: after book hit opponent has received move and is now in force mode
8263 // force the book reply into it, and then fake that it outputted this move by jumping
8264 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8266 static char bookMove[MSG_SIZ]; // a bit generous?
8268 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8269 strcat(bookMove, bookHit);
8272 programStats.nodes = programStats.depth = programStats.time =
8273 programStats.score = programStats.got_only_move = 0;
8274 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8276 if(cps->lastPing != cps->lastPong) {
8277 savedMessage = message; // args for deferred call
8279 ScheduleDelayedEvent(DeferredBookMove, 10);
8288 /* Set special modes for chess engines. Later something general
8289 * could be added here; for now there is just one kludge feature,
8290 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8291 * when "xboard" is given as an interactive command.
8293 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8294 cps->useSigint = FALSE;
8295 cps->useSigterm = FALSE;
8297 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8298 ParseFeatures(message+8, cps);
8299 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8302 if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8303 !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8304 int dummy, s=6; char buf[MSG_SIZ];
8305 if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8306 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8307 if(startedFromSetupPosition) return;
8308 ParseFEN(boards[0], &dummy, message+s);
8309 DrawPosition(TRUE, boards[0]);
8310 startedFromSetupPosition = TRUE;
8313 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8314 * want this, I was asked to put it in, and obliged.
8316 if (!strncmp(message, "setboard ", 9)) {
8317 Board initial_position;
8319 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8321 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8322 DisplayError(_("Bad FEN received from engine"), 0);
8326 CopyBoard(boards[0], initial_position);
8327 initialRulePlies = FENrulePlies;
8328 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8329 else gameMode = MachinePlaysBlack;
8330 DrawPosition(FALSE, boards[currentMove]);
8336 * Look for communication commands
8338 if (!strncmp(message, "telluser ", 9)) {
8339 if(message[9] == '\\' && message[10] == '\\')
8340 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8342 DisplayNote(message + 9);
8345 if (!strncmp(message, "tellusererror ", 14)) {
8347 if(message[14] == '\\' && message[15] == '\\')
8348 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8350 DisplayError(message + 14, 0);
8353 if (!strncmp(message, "tellopponent ", 13)) {
8354 if (appData.icsActive) {
8356 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8360 DisplayNote(message + 13);
8364 if (!strncmp(message, "tellothers ", 11)) {
8365 if (appData.icsActive) {
8367 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8373 if (!strncmp(message, "tellall ", 8)) {
8374 if (appData.icsActive) {
8376 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8380 DisplayNote(message + 8);
8384 if (strncmp(message, "warning", 7) == 0) {
8385 /* Undocumented feature, use tellusererror in new code */
8386 DisplayError(message, 0);
8389 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8390 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8391 strcat(realname, " query");
8392 AskQuestion(realname, buf2, buf1, cps->pr);
8395 /* Commands from the engine directly to ICS. We don't allow these to be
8396 * sent until we are logged on. Crafty kibitzes have been known to
8397 * interfere with the login process.
8400 if (!strncmp(message, "tellics ", 8)) {
8401 SendToICS(message + 8);
8405 if (!strncmp(message, "tellicsnoalias ", 15)) {
8406 SendToICS(ics_prefix);
8407 SendToICS(message + 15);
8411 /* The following are for backward compatibility only */
8412 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8413 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8414 SendToICS(ics_prefix);
8420 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8424 * If the move is illegal, cancel it and redraw the board.
8425 * Also deal with other error cases. Matching is rather loose
8426 * here to accommodate engines written before the spec.
8428 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8429 strncmp(message, "Error", 5) == 0) {
8430 if (StrStr(message, "name") ||
8431 StrStr(message, "rating") || StrStr(message, "?") ||
8432 StrStr(message, "result") || StrStr(message, "board") ||
8433 StrStr(message, "bk") || StrStr(message, "computer") ||
8434 StrStr(message, "variant") || StrStr(message, "hint") ||
8435 StrStr(message, "random") || StrStr(message, "depth") ||
8436 StrStr(message, "accepted")) {
8439 if (StrStr(message, "protover")) {
8440 /* Program is responding to input, so it's apparently done
8441 initializing, and this error message indicates it is
8442 protocol version 1. So we don't need to wait any longer
8443 for it to initialize and send feature commands. */
8444 FeatureDone(cps, 1);
8445 cps->protocolVersion = 1;
8448 cps->maybeThinking = FALSE;
8450 if (StrStr(message, "draw")) {
8451 /* Program doesn't have "draw" command */
8452 cps->sendDrawOffers = 0;
8455 if (cps->sendTime != 1 &&
8456 (StrStr(message, "time") || StrStr(message, "otim"))) {
8457 /* Program apparently doesn't have "time" or "otim" command */
8461 if (StrStr(message, "analyze")) {
8462 cps->analysisSupport = FALSE;
8463 cps->analyzing = FALSE;
8464 // Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8465 EditGameEvent(); // [HGM] try to preserve loaded game
8466 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8467 DisplayError(buf2, 0);
8470 if (StrStr(message, "(no matching move)st")) {
8471 /* Special kludge for GNU Chess 4 only */
8472 cps->stKludge = TRUE;
8473 SendTimeControl(cps, movesPerSession, timeControl,
8474 timeIncrement, appData.searchDepth,
8478 if (StrStr(message, "(no matching move)sd")) {
8479 /* Special kludge for GNU Chess 4 only */
8480 cps->sdKludge = TRUE;
8481 SendTimeControl(cps, movesPerSession, timeControl,
8482 timeIncrement, appData.searchDepth,
8486 if (!StrStr(message, "llegal")) {
8489 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8490 gameMode == IcsIdle) return;
8491 if (forwardMostMove <= backwardMostMove) return;
8492 if (pausing) PauseEvent();
8493 if(appData.forceIllegal) {
8494 // [HGM] illegal: machine refused move; force position after move into it
8495 SendToProgram("force\n", cps);
8496 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8497 // we have a real problem now, as SendBoard will use the a2a3 kludge
8498 // when black is to move, while there might be nothing on a2 or black
8499 // might already have the move. So send the board as if white has the move.
8500 // But first we must change the stm of the engine, as it refused the last move
8501 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8502 if(WhiteOnMove(forwardMostMove)) {
8503 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8504 SendBoard(cps, forwardMostMove); // kludgeless board
8506 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8507 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8508 SendBoard(cps, forwardMostMove+1); // kludgeless board
8510 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8511 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8512 gameMode == TwoMachinesPlay)
8513 SendToProgram("go\n", cps);
8516 if (gameMode == PlayFromGameFile) {
8517 /* Stop reading this game file */
8518 gameMode = EditGame;
8521 /* [HGM] illegal-move claim should forfeit game when Xboard */
8522 /* only passes fully legal moves */
8523 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8524 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8525 "False illegal-move claim", GE_XBOARD );
8526 return; // do not take back move we tested as valid
8528 currentMove = forwardMostMove-1;
8529 DisplayMove(currentMove-1); /* before DisplayMoveError */
8530 SwitchClocks(forwardMostMove-1); // [HGM] race
8531 DisplayBothClocks();
8532 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8533 parseList[currentMove], _(cps->which));
8534 DisplayMoveError(buf1);
8535 DrawPosition(FALSE, boards[currentMove]);
8537 SetUserThinkingEnables();
8540 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8541 /* Program has a broken "time" command that
8542 outputs a string not ending in newline.
8548 * If chess program startup fails, exit with an error message.
8549 * Attempts to recover here are futile. [HGM] Well, we try anyway
8551 if ((StrStr(message, "unknown host") != NULL)
8552 || (StrStr(message, "No remote directory") != NULL)
8553 || (StrStr(message, "not found") != NULL)
8554 || (StrStr(message, "No such file") != NULL)
8555 || (StrStr(message, "can't alloc") != NULL)
8556 || (StrStr(message, "Permission denied") != NULL)) {
8558 cps->maybeThinking = FALSE;
8559 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8560 _(cps->which), cps->program, cps->host, message);
8561 RemoveInputSource(cps->isr);
8562 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8563 if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8564 if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8570 * Look for hint output
8572 if (sscanf(message, "Hint: %s", buf1) == 1) {
8573 if (cps == &first && hintRequested) {
8574 hintRequested = FALSE;
8575 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8576 &fromX, &fromY, &toX, &toY, &promoChar)) {
8577 (void) CoordsToAlgebraic(boards[forwardMostMove],
8578 PosFlags(forwardMostMove),
8579 fromY, fromX, toY, toX, promoChar, buf1);
8580 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8581 DisplayInformation(buf2);
8583 /* Hint move could not be parsed!? */
8584 snprintf(buf2, sizeof(buf2),
8585 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8586 buf1, _(cps->which));
8587 DisplayError(buf2, 0);
8590 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8596 * Ignore other messages if game is not in progress
8598 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8599 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8602 * look for win, lose, draw, or draw offer
8604 if (strncmp(message, "1-0", 3) == 0) {
8605 char *p, *q, *r = "";
8606 p = strchr(message, '{');
8614 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8616 } else if (strncmp(message, "0-1", 3) == 0) {
8617 char *p, *q, *r = "";
8618 p = strchr(message, '{');
8626 /* Kludge for Arasan 4.1 bug */
8627 if (strcmp(r, "Black resigns") == 0) {
8628 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8631 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8633 } else if (strncmp(message, "1/2", 3) == 0) {
8634 char *p, *q, *r = "";
8635 p = strchr(message, '{');
8644 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8647 } else if (strncmp(message, "White resign", 12) == 0) {
8648 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8650 } else if (strncmp(message, "Black resign", 12) == 0) {
8651 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8653 } else if (strncmp(message, "White matches", 13) == 0 ||
8654 strncmp(message, "Black matches", 13) == 0 ) {
8655 /* [HGM] ignore GNUShogi noises */
8657 } else if (strncmp(message, "White", 5) == 0 &&
8658 message[5] != '(' &&
8659 StrStr(message, "Black") == NULL) {
8660 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8662 } else if (strncmp(message, "Black", 5) == 0 &&
8663 message[5] != '(') {
8664 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8666 } else if (strcmp(message, "resign") == 0 ||
8667 strcmp(message, "computer resigns") == 0) {
8669 case MachinePlaysBlack:
8670 case IcsPlayingBlack:
8671 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8673 case MachinePlaysWhite:
8674 case IcsPlayingWhite:
8675 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8677 case TwoMachinesPlay:
8678 if (cps->twoMachinesColor[0] == 'w')
8679 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8681 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8688 } else if (strncmp(message, "opponent mates", 14) == 0) {
8690 case MachinePlaysBlack:
8691 case IcsPlayingBlack:
8692 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8694 case MachinePlaysWhite:
8695 case IcsPlayingWhite:
8696 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8698 case TwoMachinesPlay:
8699 if (cps->twoMachinesColor[0] == 'w')
8700 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8702 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8709 } else if (strncmp(message, "computer mates", 14) == 0) {
8711 case MachinePlaysBlack:
8712 case IcsPlayingBlack:
8713 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8715 case MachinePlaysWhite:
8716 case IcsPlayingWhite:
8717 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8719 case TwoMachinesPlay:
8720 if (cps->twoMachinesColor[0] == 'w')
8721 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8723 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8730 } else if (strncmp(message, "checkmate", 9) == 0) {
8731 if (WhiteOnMove(forwardMostMove)) {
8732 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8734 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8737 } else if (strstr(message, "Draw") != NULL ||
8738 strstr(message, "game is a draw") != NULL) {
8739 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8741 } else if (strstr(message, "offer") != NULL &&
8742 strstr(message, "draw") != NULL) {
8744 if (appData.zippyPlay && first.initDone) {
8745 /* Relay offer to ICS */
8746 SendToICS(ics_prefix);
8747 SendToICS("draw\n");
8750 cps->offeredDraw = 2; /* valid until this engine moves twice */
8751 if (gameMode == TwoMachinesPlay) {
8752 if (cps->other->offeredDraw) {
8753 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8754 /* [HGM] in two-machine mode we delay relaying draw offer */
8755 /* until after we also have move, to see if it is really claim */
8757 } else if (gameMode == MachinePlaysWhite ||
8758 gameMode == MachinePlaysBlack) {
8759 if (userOfferedDraw) {
8760 DisplayInformation(_("Machine accepts your draw offer"));
8761 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8763 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8770 * Look for thinking output
8772 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8773 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8775 int plylev, mvleft, mvtot, curscore, time;
8776 char mvname[MOVE_LEN];
8780 int prefixHint = FALSE;
8781 mvname[0] = NULLCHAR;
8784 case MachinePlaysBlack:
8785 case IcsPlayingBlack:
8786 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8788 case MachinePlaysWhite:
8789 case IcsPlayingWhite:
8790 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8795 case IcsObserving: /* [DM] icsEngineAnalyze */
8796 if (!appData.icsEngineAnalyze) ignore = TRUE;
8798 case TwoMachinesPlay:
8799 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8809 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8811 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8812 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8814 if (plyext != ' ' && plyext != '\t') {
8818 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8819 if( cps->scoreIsAbsolute &&
8820 ( gameMode == MachinePlaysBlack ||
8821 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8822 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8823 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8824 !WhiteOnMove(currentMove)
8827 curscore = -curscore;
8830 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8832 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8835 snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8836 buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8837 gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8838 if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8839 if(f = fopen(buf, "w")) { // export PV to applicable PV file
8840 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8842 } else DisplayError(_("failed writing PV"), 0);
8845 tempStats.depth = plylev;
8846 tempStats.nodes = nodes;
8847 tempStats.time = time;
8848 tempStats.score = curscore;
8849 tempStats.got_only_move = 0;
8851 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8854 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8855 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8856 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8857 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8858 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8859 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8860 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8861 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8864 /* Buffer overflow protection */
8865 if (pv[0] != NULLCHAR) {
8866 if (strlen(pv) >= sizeof(tempStats.movelist)
8867 && appData.debugMode) {
8869 "PV is too long; using the first %u bytes.\n",
8870 (unsigned) sizeof(tempStats.movelist) - 1);
8873 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8875 sprintf(tempStats.movelist, " no PV\n");
8878 if (tempStats.seen_stat) {
8879 tempStats.ok_to_send = 1;
8882 if (strchr(tempStats.movelist, '(') != NULL) {
8883 tempStats.line_is_book = 1;
8884 tempStats.nr_moves = 0;
8885 tempStats.moves_left = 0;
8887 tempStats.line_is_book = 0;
8890 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8891 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8893 SendProgramStatsToFrontend( cps, &tempStats );
8896 [AS] Protect the thinkOutput buffer from overflow... this
8897 is only useful if buf1 hasn't overflowed first!
8899 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8901 (gameMode == TwoMachinesPlay ?
8902 ToUpper(cps->twoMachinesColor[0]) : ' '),
8903 ((double) curscore) / 100.0,
8904 prefixHint ? lastHint : "",
8905 prefixHint ? " " : "" );
8907 if( buf1[0] != NULLCHAR ) {
8908 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8910 if( strlen(pv) > max_len ) {
8911 if( appData.debugMode) {
8912 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8914 pv[max_len+1] = '\0';
8917 strcat( thinkOutput, pv);
8920 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8921 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8922 DisplayMove(currentMove - 1);
8926 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8927 /* crafty (9.25+) says "(only move) <move>"
8928 * if there is only 1 legal move
8930 sscanf(p, "(only move) %s", buf1);
8931 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8932 sprintf(programStats.movelist, "%s (only move)", buf1);
8933 programStats.depth = 1;
8934 programStats.nr_moves = 1;
8935 programStats.moves_left = 1;
8936 programStats.nodes = 1;
8937 programStats.time = 1;
8938 programStats.got_only_move = 1;
8940 /* Not really, but we also use this member to
8941 mean "line isn't going to change" (Crafty
8942 isn't searching, so stats won't change) */
8943 programStats.line_is_book = 1;
8945 SendProgramStatsToFrontend( cps, &programStats );
8947 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8948 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8949 DisplayMove(currentMove - 1);
8952 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8953 &time, &nodes, &plylev, &mvleft,
8954 &mvtot, mvname) >= 5) {
8955 /* The stat01: line is from Crafty (9.29+) in response
8956 to the "." command */
8957 programStats.seen_stat = 1;
8958 cps->maybeThinking = TRUE;
8960 if (programStats.got_only_move || !appData.periodicUpdates)
8963 programStats.depth = plylev;
8964 programStats.time = time;
8965 programStats.nodes = nodes;
8966 programStats.moves_left = mvleft;
8967 programStats.nr_moves = mvtot;
8968 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8969 programStats.ok_to_send = 1;
8970 programStats.movelist[0] = '\0';
8972 SendProgramStatsToFrontend( cps, &programStats );
8976 } else if (strncmp(message,"++",2) == 0) {
8977 /* Crafty 9.29+ outputs this */
8978 programStats.got_fail = 2;
8981 } else if (strncmp(message,"--",2) == 0) {
8982 /* Crafty 9.29+ outputs this */
8983 programStats.got_fail = 1;
8986 } else if (thinkOutput[0] != NULLCHAR &&
8987 strncmp(message, " ", 4) == 0) {
8988 unsigned message_len;
8991 while (*p && *p == ' ') p++;
8993 message_len = strlen( p );
8995 /* [AS] Avoid buffer overflow */
8996 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8997 strcat(thinkOutput, " ");
8998 strcat(thinkOutput, p);
9001 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9002 strcat(programStats.movelist, " ");
9003 strcat(programStats.movelist, p);
9006 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9007 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9008 DisplayMove(currentMove - 1);
9016 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9017 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9019 ChessProgramStats cpstats;
9021 if (plyext != ' ' && plyext != '\t') {
9025 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9026 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9027 curscore = -curscore;
9030 cpstats.depth = plylev;
9031 cpstats.nodes = nodes;
9032 cpstats.time = time;
9033 cpstats.score = curscore;
9034 cpstats.got_only_move = 0;
9035 cpstats.movelist[0] = '\0';
9037 if (buf1[0] != NULLCHAR) {
9038 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9041 cpstats.ok_to_send = 0;
9042 cpstats.line_is_book = 0;
9043 cpstats.nr_moves = 0;
9044 cpstats.moves_left = 0;
9046 SendProgramStatsToFrontend( cps, &cpstats );
9053 /* Parse a game score from the character string "game", and
9054 record it as the history of the current game. The game
9055 score is NOT assumed to start from the standard position.
9056 The display is not updated in any way.
9059 ParseGameHistory (char *game)
9062 int fromX, fromY, toX, toY, boardIndex;
9067 if (appData.debugMode)
9068 fprintf(debugFP, "Parsing game history: %s\n", game);
9070 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9071 gameInfo.site = StrSave(appData.icsHost);
9072 gameInfo.date = PGNDate();
9073 gameInfo.round = StrSave("-");
9075 /* Parse out names of players */
9076 while (*game == ' ') game++;
9078 while (*game != ' ') *p++ = *game++;
9080 gameInfo.white = StrSave(buf);
9081 while (*game == ' ') game++;
9083 while (*game != ' ' && *game != '\n') *p++ = *game++;
9085 gameInfo.black = StrSave(buf);
9088 boardIndex = blackPlaysFirst ? 1 : 0;
9091 yyboardindex = boardIndex;
9092 moveType = (ChessMove) Myylex();
9094 case IllegalMove: /* maybe suicide chess, etc. */
9095 if (appData.debugMode) {
9096 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9097 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9098 setbuf(debugFP, NULL);
9100 case WhitePromotion:
9101 case BlackPromotion:
9102 case WhiteNonPromotion:
9103 case BlackNonPromotion:
9105 case WhiteCapturesEnPassant:
9106 case BlackCapturesEnPassant:
9107 case WhiteKingSideCastle:
9108 case WhiteQueenSideCastle:
9109 case BlackKingSideCastle:
9110 case BlackQueenSideCastle:
9111 case WhiteKingSideCastleWild:
9112 case WhiteQueenSideCastleWild:
9113 case BlackKingSideCastleWild:
9114 case BlackQueenSideCastleWild:
9116 case WhiteHSideCastleFR:
9117 case WhiteASideCastleFR:
9118 case BlackHSideCastleFR:
9119 case BlackASideCastleFR:
9121 fromX = currentMoveString[0] - AAA;
9122 fromY = currentMoveString[1] - ONE;
9123 toX = currentMoveString[2] - AAA;
9124 toY = currentMoveString[3] - ONE;
9125 promoChar = currentMoveString[4];
9129 if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9130 fromX = moveType == WhiteDrop ?
9131 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9132 (int) CharToPiece(ToLower(currentMoveString[0]));
9134 toX = currentMoveString[2] - AAA;
9135 toY = currentMoveString[3] - ONE;
9136 promoChar = NULLCHAR;
9140 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9141 if (appData.debugMode) {
9142 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9143 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9144 setbuf(debugFP, NULL);
9146 DisplayError(buf, 0);
9148 case ImpossibleMove:
9150 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9151 if (appData.debugMode) {
9152 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9153 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9154 setbuf(debugFP, NULL);
9156 DisplayError(buf, 0);
9159 if (boardIndex < backwardMostMove) {
9160 /* Oops, gap. How did that happen? */
9161 DisplayError(_("Gap in move list"), 0);
9164 backwardMostMove = blackPlaysFirst ? 1 : 0;
9165 if (boardIndex > forwardMostMove) {
9166 forwardMostMove = boardIndex;
9170 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9171 strcat(parseList[boardIndex-1], " ");
9172 strcat(parseList[boardIndex-1], yy_text);
9184 case GameUnfinished:
9185 if (gameMode == IcsExamining) {
9186 if (boardIndex < backwardMostMove) {
9187 /* Oops, gap. How did that happen? */
9190 backwardMostMove = blackPlaysFirst ? 1 : 0;
9193 gameInfo.result = moveType;
9194 p = strchr(yy_text, '{');
9195 if (p == NULL) p = strchr(yy_text, '(');
9198 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9200 q = strchr(p, *p == '{' ? '}' : ')');
9201 if (q != NULL) *q = NULLCHAR;
9204 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9205 gameInfo.resultDetails = StrSave(p);
9208 if (boardIndex >= forwardMostMove &&
9209 !(gameMode == IcsObserving && ics_gamenum == -1)) {
9210 backwardMostMove = blackPlaysFirst ? 1 : 0;
9213 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9214 fromY, fromX, toY, toX, promoChar,
9215 parseList[boardIndex]);
9216 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9217 /* currentMoveString is set as a side-effect of yylex */
9218 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9219 strcat(moveList[boardIndex], "\n");
9221 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9222 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9228 if(gameInfo.variant != VariantShogi)
9229 strcat(parseList[boardIndex - 1], "+");
9233 strcat(parseList[boardIndex - 1], "#");
9240 /* Apply a move to the given board */
9242 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9244 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9245 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9247 /* [HGM] compute & store e.p. status and castling rights for new position */
9248 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9250 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9251 oldEP = (signed char)board[EP_STATUS];
9252 board[EP_STATUS] = EP_NONE;
9254 if (fromY == DROP_RANK) {
9256 if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9257 board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9260 piece = board[toY][toX] = (ChessSquare) fromX;
9264 if( board[toY][toX] != EmptySquare )
9265 board[EP_STATUS] = EP_CAPTURE;
9267 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9268 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9269 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9271 if( board[fromY][fromX] == WhitePawn ) {
9272 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9273 board[EP_STATUS] = EP_PAWN_MOVE;
9275 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9276 gameInfo.variant != VariantBerolina || toX < fromX)
9277 board[EP_STATUS] = toX | berolina;
9278 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9279 gameInfo.variant != VariantBerolina || toX > fromX)
9280 board[EP_STATUS] = toX;
9283 if( board[fromY][fromX] == BlackPawn ) {
9284 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9285 board[EP_STATUS] = EP_PAWN_MOVE;
9286 if( toY-fromY== -2) {
9287 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9288 gameInfo.variant != VariantBerolina || toX < fromX)
9289 board[EP_STATUS] = toX | berolina;
9290 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9291 gameInfo.variant != VariantBerolina || toX > fromX)
9292 board[EP_STATUS] = toX;
9296 for(i=0; i<nrCastlingRights; i++) {
9297 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9298 board[CASTLING][i] == toX && castlingRank[i] == toY
9299 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9302 if (fromX == toX && fromY == toY) return;
9304 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9305 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9306 if(gameInfo.variant == VariantKnightmate)
9307 king += (int) WhiteUnicorn - (int) WhiteKing;
9309 /* Code added by Tord: */
9310 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9311 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9312 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9313 board[fromY][fromX] = EmptySquare;
9314 board[toY][toX] = EmptySquare;
9315 if((toX > fromX) != (piece == WhiteRook)) {
9316 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9318 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9320 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9321 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9322 board[fromY][fromX] = EmptySquare;
9323 board[toY][toX] = EmptySquare;
9324 if((toX > fromX) != (piece == BlackRook)) {
9325 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9327 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9329 /* End of code added by Tord */
9331 } else if (board[fromY][fromX] == king
9332 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9333 && toY == fromY && toX > fromX+1) {
9334 board[fromY][fromX] = EmptySquare;
9335 board[toY][toX] = king;
9336 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9337 board[fromY][BOARD_RGHT-1] = EmptySquare;
9338 } else if (board[fromY][fromX] == king
9339 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9340 && toY == fromY && toX < fromX-1) {
9341 board[fromY][fromX] = EmptySquare;
9342 board[toY][toX] = king;
9343 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9344 board[fromY][BOARD_LEFT] = EmptySquare;
9345 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9346 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9347 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9349 /* white pawn promotion */
9350 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9351 if(gameInfo.variant==VariantBughouse ||
9352 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9353 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9354 board[fromY][fromX] = EmptySquare;
9355 } else if ((fromY >= BOARD_HEIGHT>>1)
9357 && gameInfo.variant != VariantXiangqi
9358 && gameInfo.variant != VariantBerolina
9359 && (board[fromY][fromX] == WhitePawn)
9360 && (board[toY][toX] == EmptySquare)) {
9361 board[fromY][fromX] = EmptySquare;
9362 board[toY][toX] = WhitePawn;
9363 captured = board[toY - 1][toX];
9364 board[toY - 1][toX] = EmptySquare;
9365 } else if ((fromY == BOARD_HEIGHT-4)
9367 && gameInfo.variant == VariantBerolina
9368 && (board[fromY][fromX] == WhitePawn)
9369 && (board[toY][toX] == EmptySquare)) {
9370 board[fromY][fromX] = EmptySquare;
9371 board[toY][toX] = WhitePawn;
9372 if(oldEP & EP_BEROLIN_A) {
9373 captured = board[fromY][fromX-1];
9374 board[fromY][fromX-1] = EmptySquare;
9375 }else{ captured = board[fromY][fromX+1];
9376 board[fromY][fromX+1] = EmptySquare;
9378 } else if (board[fromY][fromX] == king
9379 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9380 && toY == fromY && toX > fromX+1) {
9381 board[fromY][fromX] = EmptySquare;
9382 board[toY][toX] = king;
9383 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9384 board[fromY][BOARD_RGHT-1] = EmptySquare;
9385 } else if (board[fromY][fromX] == king
9386 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9387 && toY == fromY && toX < fromX-1) {
9388 board[fromY][fromX] = EmptySquare;
9389 board[toY][toX] = king;
9390 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9391 board[fromY][BOARD_LEFT] = EmptySquare;
9392 } else if (fromY == 7 && fromX == 3
9393 && board[fromY][fromX] == BlackKing
9394 && toY == 7 && toX == 5) {
9395 board[fromY][fromX] = EmptySquare;
9396 board[toY][toX] = BlackKing;
9397 board[fromY][7] = EmptySquare;
9398 board[toY][4] = BlackRook;
9399 } else if (fromY == 7 && fromX == 3
9400 && board[fromY][fromX] == BlackKing
9401 && toY == 7 && toX == 1) {
9402 board[fromY][fromX] = EmptySquare;
9403 board[toY][toX] = BlackKing;
9404 board[fromY][0] = EmptySquare;
9405 board[toY][2] = BlackRook;
9406 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9407 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9408 && toY < promoRank && promoChar
9410 /* black pawn promotion */
9411 board[toY][toX] = CharToPiece(ToLower(promoChar));
9412 if(gameInfo.variant==VariantBughouse ||
9413 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9414 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9415 board[fromY][fromX] = EmptySquare;
9416 } else if ((fromY < BOARD_HEIGHT>>1)
9418 && gameInfo.variant != VariantXiangqi
9419 && gameInfo.variant != VariantBerolina
9420 && (board[fromY][fromX] == BlackPawn)
9421 && (board[toY][toX] == EmptySquare)) {
9422 board[fromY][fromX] = EmptySquare;
9423 board[toY][toX] = BlackPawn;
9424 captured = board[toY + 1][toX];
9425 board[toY + 1][toX] = EmptySquare;
9426 } else if ((fromY == 3)
9428 && gameInfo.variant == VariantBerolina
9429 && (board[fromY][fromX] == BlackPawn)
9430 && (board[toY][toX] == EmptySquare)) {
9431 board[fromY][fromX] = EmptySquare;
9432 board[toY][toX] = BlackPawn;
9433 if(oldEP & EP_BEROLIN_A) {
9434 captured = board[fromY][fromX-1];
9435 board[fromY][fromX-1] = EmptySquare;
9436 }else{ captured = board[fromY][fromX+1];
9437 board[fromY][fromX+1] = EmptySquare;
9440 board[toY][toX] = board[fromY][fromX];
9441 board[fromY][fromX] = EmptySquare;
9445 if (gameInfo.holdingsWidth != 0) {
9447 /* !!A lot more code needs to be written to support holdings */
9448 /* [HGM] OK, so I have written it. Holdings are stored in the */
9449 /* penultimate board files, so they are automaticlly stored */
9450 /* in the game history. */
9451 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9452 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9453 /* Delete from holdings, by decreasing count */
9454 /* and erasing image if necessary */
9455 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9456 if(p < (int) BlackPawn) { /* white drop */
9457 p -= (int)WhitePawn;
9458 p = PieceToNumber((ChessSquare)p);
9459 if(p >= gameInfo.holdingsSize) p = 0;
9460 if(--board[p][BOARD_WIDTH-2] <= 0)
9461 board[p][BOARD_WIDTH-1] = EmptySquare;
9462 if((int)board[p][BOARD_WIDTH-2] < 0)
9463 board[p][BOARD_WIDTH-2] = 0;
9464 } else { /* black drop */
9465 p -= (int)BlackPawn;
9466 p = PieceToNumber((ChessSquare)p);
9467 if(p >= gameInfo.holdingsSize) p = 0;
9468 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9469 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9470 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9471 board[BOARD_HEIGHT-1-p][1] = 0;
9474 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9475 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9476 /* [HGM] holdings: Add to holdings, if holdings exist */
9477 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9478 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9479 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9482 if (p >= (int) BlackPawn) {
9483 p -= (int)BlackPawn;
9484 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9485 /* in Shogi restore piece to its original first */
9486 captured = (ChessSquare) (DEMOTED captured);
9489 p = PieceToNumber((ChessSquare)p);
9490 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9491 board[p][BOARD_WIDTH-2]++;
9492 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9494 p -= (int)WhitePawn;
9495 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9496 captured = (ChessSquare) (DEMOTED captured);
9499 p = PieceToNumber((ChessSquare)p);
9500 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9501 board[BOARD_HEIGHT-1-p][1]++;
9502 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9505 } else if (gameInfo.variant == VariantAtomic) {
9506 if (captured != EmptySquare) {
9508 for (y = toY-1; y <= toY+1; y++) {
9509 for (x = toX-1; x <= toX+1; x++) {
9510 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9511 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9512 board[y][x] = EmptySquare;
9516 board[toY][toX] = EmptySquare;
9519 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9520 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9522 if(promoChar == '+') {
9523 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9524 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9525 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9526 ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9527 if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9528 && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9529 board[toY][toX] = newPiece;
9531 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9532 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9533 // [HGM] superchess: take promotion piece out of holdings
9534 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9535 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9536 if(!--board[k][BOARD_WIDTH-2])
9537 board[k][BOARD_WIDTH-1] = EmptySquare;
9539 if(!--board[BOARD_HEIGHT-1-k][1])
9540 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9546 /* Updates forwardMostMove */
9548 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9550 // forwardMostMove++; // [HGM] bare: moved downstream
9552 (void) CoordsToAlgebraic(boards[forwardMostMove],
9553 PosFlags(forwardMostMove),
9554 fromY, fromX, toY, toX, promoChar,
9555 parseList[forwardMostMove]);
9557 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9558 int timeLeft; static int lastLoadFlag=0; int king, piece;
9559 piece = boards[forwardMostMove][fromY][fromX];
9560 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9561 if(gameInfo.variant == VariantKnightmate)
9562 king += (int) WhiteUnicorn - (int) WhiteKing;
9563 if(forwardMostMove == 0) {
9564 if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9565 fprintf(serverMoves, "%s;", UserName());
9566 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9567 fprintf(serverMoves, "%s;", second.tidy);
9568 fprintf(serverMoves, "%s;", first.tidy);
9569 if(gameMode == MachinePlaysWhite)
9570 fprintf(serverMoves, "%s;", UserName());
9571 else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9572 fprintf(serverMoves, "%s;", second.tidy);
9573 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9574 lastLoadFlag = loadFlag;
9576 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9577 // print castling suffix
9578 if( toY == fromY && piece == king ) {
9580 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9582 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9585 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9586 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9587 boards[forwardMostMove][toY][toX] == EmptySquare
9588 && fromX != toX && fromY != toY)
9589 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9591 if(promoChar != NULLCHAR)
9592 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9594 char buf[MOVE_LEN*2], *p; int len;
9595 fprintf(serverMoves, "/%d/%d",
9596 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9597 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9598 else timeLeft = blackTimeRemaining/1000;
9599 fprintf(serverMoves, "/%d", timeLeft);
9600 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9601 if(p = strchr(buf, '=')) *p = NULLCHAR;
9602 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9603 fprintf(serverMoves, "/%s", buf);
9605 fflush(serverMoves);
9608 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9609 GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9612 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9613 if (commentList[forwardMostMove+1] != NULL) {
9614 free(commentList[forwardMostMove+1]);
9615 commentList[forwardMostMove+1] = NULL;
9617 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9618 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9619 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9620 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9621 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9622 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9623 adjustedClock = FALSE;
9624 gameInfo.result = GameUnfinished;
9625 if (gameInfo.resultDetails != NULL) {
9626 free(gameInfo.resultDetails);
9627 gameInfo.resultDetails = NULL;
9629 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9630 moveList[forwardMostMove - 1]);
9631 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9637 if(gameInfo.variant != VariantShogi)
9638 strcat(parseList[forwardMostMove - 1], "+");
9642 strcat(parseList[forwardMostMove - 1], "#");
9648 /* Updates currentMove if not pausing */
9650 ShowMove (int fromX, int fromY, int toX, int toY)
9652 int instant = (gameMode == PlayFromGameFile) ?
9653 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9654 if(appData.noGUI) return;
9655 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9657 if (forwardMostMove == currentMove + 1) {
9658 AnimateMove(boards[forwardMostMove - 1],
9659 fromX, fromY, toX, toY);
9661 if (appData.highlightLastMove) {
9662 SetHighlights(fromX, fromY, toX, toY);
9665 currentMove = forwardMostMove;
9668 if (instant) return;
9670 DisplayMove(currentMove - 1);
9671 DrawPosition(FALSE, boards[currentMove]);
9672 DisplayBothClocks();
9673 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9677 SendEgtPath (ChessProgramState *cps)
9678 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9679 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9681 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9684 char c, *q = name+1, *r, *s;
9686 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9687 while(*p && *p != ',') *q++ = *p++;
9689 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9690 strcmp(name, ",nalimov:") == 0 ) {
9691 // take nalimov path from the menu-changeable option first, if it is defined
9692 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9693 SendToProgram(buf,cps); // send egtbpath command for nalimov
9695 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9696 (s = StrStr(appData.egtFormats, name)) != NULL) {
9697 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9698 s = r = StrStr(s, ":") + 1; // beginning of path info
9699 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9700 c = *r; *r = 0; // temporarily null-terminate path info
9701 *--q = 0; // strip of trailig ':' from name
9702 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9704 SendToProgram(buf,cps); // send egtbpath command for this format
9706 if(*p == ',') p++; // read away comma to position for next format name
9711 InitChessProgram (ChessProgramState *cps, int setup)
9712 /* setup needed to setup FRC opening position */
9714 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9715 if (appData.noChessProgram) return;
9716 hintRequested = FALSE;
9717 bookRequested = FALSE;
9719 ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9720 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9721 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9722 if(cps->memSize) { /* [HGM] memory */
9723 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9724 SendToProgram(buf, cps);
9726 SendEgtPath(cps); /* [HGM] EGT */
9727 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9728 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9729 SendToProgram(buf, cps);
9732 SendToProgram(cps->initString, cps);
9733 if (gameInfo.variant != VariantNormal &&
9734 gameInfo.variant != VariantLoadable
9735 /* [HGM] also send variant if board size non-standard */
9736 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9738 char *v = VariantName(gameInfo.variant);
9739 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9740 /* [HGM] in protocol 1 we have to assume all variants valid */
9741 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9742 DisplayFatalError(buf, 0, 1);
9746 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9747 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9748 if( gameInfo.variant == VariantXiangqi )
9749 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9750 if( gameInfo.variant == VariantShogi )
9751 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9752 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9753 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9754 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9755 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9756 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9757 if( gameInfo.variant == VariantCourier )
9758 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9759 if( gameInfo.variant == VariantSuper )
9760 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9761 if( gameInfo.variant == VariantGreat )
9762 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9763 if( gameInfo.variant == VariantSChess )
9764 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9765 if( gameInfo.variant == VariantGrand )
9766 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9769 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9770 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9771 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9772 if(StrStr(cps->variants, b) == NULL) {
9773 // specific sized variant not known, check if general sizing allowed
9774 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9775 if(StrStr(cps->variants, "boardsize") == NULL) {
9776 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9777 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9778 DisplayFatalError(buf, 0, 1);
9781 /* [HGM] here we really should compare with the maximum supported board size */
9784 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9785 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9786 SendToProgram(buf, cps);
9788 currentlyInitializedVariant = gameInfo.variant;
9790 /* [HGM] send opening position in FRC to first engine */
9792 SendToProgram("force\n", cps);
9794 /* engine is now in force mode! Set flag to wake it up after first move. */
9795 setboardSpoiledMachineBlack = 1;
9799 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9800 SendToProgram(buf, cps);
9802 cps->maybeThinking = FALSE;
9803 cps->offeredDraw = 0;
9804 if (!appData.icsActive) {
9805 SendTimeControl(cps, movesPerSession, timeControl,
9806 timeIncrement, appData.searchDepth,
9809 if (appData.showThinking
9810 // [HGM] thinking: four options require thinking output to be sent
9811 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9813 SendToProgram("post\n", cps);
9815 SendToProgram("hard\n", cps);
9816 if (!appData.ponderNextMove) {
9817 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9818 it without being sure what state we are in first. "hard"
9819 is not a toggle, so that one is OK.
9821 SendToProgram("easy\n", cps);
9824 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9825 SendToProgram(buf, cps);
9827 cps->initDone = TRUE;
9828 ClearEngineOutputPane(cps == &second);
9833 StartChessProgram (ChessProgramState *cps)
9838 if (appData.noChessProgram) return;
9839 cps->initDone = FALSE;
9841 if (strcmp(cps->host, "localhost") == 0) {
9842 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9843 } else if (*appData.remoteShell == NULLCHAR) {
9844 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9846 if (*appData.remoteUser == NULLCHAR) {
9847 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9850 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9851 cps->host, appData.remoteUser, cps->program);
9853 err = StartChildProcess(buf, "", &cps->pr);
9857 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9858 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9859 if(cps != &first) return;
9860 appData.noChessProgram = TRUE;
9863 // DisplayFatalError(buf, err, 1);
9864 // cps->pr = NoProc;
9869 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9870 if (cps->protocolVersion > 1) {
9871 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9872 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9873 cps->comboCnt = 0; // and values of combo boxes
9874 SendToProgram(buf, cps);
9876 SendToProgram("xboard\n", cps);
9881 TwoMachinesEventIfReady P((void))
9883 static int curMess = 0;
9884 if (first.lastPing != first.lastPong) {
9885 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9886 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9889 if (second.lastPing != second.lastPong) {
9890 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9891 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9894 DisplayMessage("", ""); curMess = 0;
9900 MakeName (char *template)
9904 static char buf[MSG_SIZ];
9908 clock = time((time_t *)NULL);
9909 tm = localtime(&clock);
9911 while(*p++ = *template++) if(p[-1] == '%') {
9912 switch(*template++) {
9913 case 0: *p = 0; return buf;
9914 case 'Y': i = tm->tm_year+1900; break;
9915 case 'y': i = tm->tm_year-100; break;
9916 case 'M': i = tm->tm_mon+1; break;
9917 case 'd': i = tm->tm_mday; break;
9918 case 'h': i = tm->tm_hour; break;
9919 case 'm': i = tm->tm_min; break;
9920 case 's': i = tm->tm_sec; break;
9923 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9929 CountPlayers (char *p)
9932 while(p = strchr(p, '\n')) p++, n++; // count participants
9937 WriteTourneyFile (char *results, FILE *f)
9938 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9939 if(f == NULL) f = fopen(appData.tourneyFile, "w");
9940 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9941 // create a file with tournament description
9942 fprintf(f, "-participants {%s}\n", appData.participants);
9943 fprintf(f, "-seedBase %d\n", appData.seedBase);
9944 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9945 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9946 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9947 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9948 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9949 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9950 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9951 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9952 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9953 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9954 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9955 fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9957 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9959 fprintf(f, "-mps %d\n", appData.movesPerSession);
9960 fprintf(f, "-tc %s\n", appData.timeControl);
9961 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9963 fprintf(f, "-results \"%s\"\n", results);
9968 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9971 Substitute (char *participants, int expunge)
9973 int i, changed, changes=0, nPlayers=0;
9974 char *p, *q, *r, buf[MSG_SIZ];
9975 if(participants == NULL) return;
9976 if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9977 r = p = participants; q = appData.participants;
9978 while(*p && *p == *q) {
9979 if(*p == '\n') r = p+1, nPlayers++;
9982 if(*p) { // difference
9983 while(*p && *p++ != '\n');
9984 while(*q && *q++ != '\n');
9986 changes = 1 + (strcmp(p, q) != 0);
9988 if(changes == 1) { // a single engine mnemonic was changed
9989 q = r; while(*q) nPlayers += (*q++ == '\n');
9990 p = buf; while(*r && (*p = *r++) != '\n') p++;
9992 NamesToList(firstChessProgramNames, command, mnemonic, "all");
9993 for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9994 if(mnemonic[i]) { // The substitute is valid
9996 if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9997 flock(fileno(f), LOCK_EX);
9998 ParseArgsFromFile(f);
9999 fseek(f, 0, SEEK_SET);
10000 FREE(appData.participants); appData.participants = participants;
10001 if(expunge) { // erase results of replaced engine
10002 int len = strlen(appData.results), w, b, dummy;
10003 for(i=0; i<len; i++) {
10004 Pairing(i, nPlayers, &w, &b, &dummy);
10005 if((w == changed || b == changed) && appData.results[i] == '*') {
10006 DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10011 for(i=0; i<len; i++) {
10012 Pairing(i, nPlayers, &w, &b, &dummy);
10013 if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10016 WriteTourneyFile(appData.results, f);
10017 fclose(f); // release lock
10020 } else DisplayError(_("No engine with the name you gave is installed"), 0);
10022 if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10023 if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
10024 free(participants);
10029 CreateTourney (char *name)
10032 if(matchMode && strcmp(name, appData.tourneyFile)) {
10033 ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10035 if(name[0] == NULLCHAR) {
10036 if(appData.participants[0])
10037 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10040 f = fopen(name, "r");
10041 if(f) { // file exists
10042 ASSIGN(appData.tourneyFile, name);
10043 ParseArgsFromFile(f); // parse it
10045 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10046 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10047 DisplayError(_("Not enough participants"), 0);
10050 ASSIGN(appData.tourneyFile, name);
10051 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10052 if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10055 appData.noChessProgram = FALSE;
10056 appData.clockMode = TRUE;
10062 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10064 char buf[MSG_SIZ], *p, *q;
10065 int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10066 insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10067 skip = !all && group[0]; // if group requested, we start in skip mode
10068 for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10069 p = names; q = buf; header = 0;
10070 while(*p && *p != '\n') *q++ = *p++;
10072 if(*p == '\n') p++;
10073 if(buf[0] == '#') {
10074 if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10075 depth++; // we must be entering a new group
10076 if(all) continue; // suppress printing group headers when complete list requested
10078 if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10080 if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10081 if(engineList[i]) free(engineList[i]);
10082 engineList[i] = strdup(buf);
10083 if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10084 if(engineMnemonic[i]) free(engineMnemonic[i]);
10085 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10087 sscanf(q + 8, "%s", buf + strlen(buf));
10090 engineMnemonic[i] = strdup(buf);
10093 engineList[i] = engineMnemonic[i] = NULL;
10097 // following implemented as macro to avoid type limitations
10098 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10101 SwapEngines (int n)
10102 { // swap settings for first engine and other engine (so far only some selected options)
10107 SWAP(chessProgram, p)
10109 SWAP(hasOwnBookUCI, h)
10110 SWAP(protocolVersion, h)
10112 SWAP(scoreIsAbsolute, h)
10117 SWAP(engOptions, p)
10118 SWAP(engInitString, p)
10119 SWAP(computerString, p)
10121 SWAP(fenOverride, p)
10123 SWAP(accumulateTC, h)
10128 SetPlayer (int player, char *p)
10129 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
10131 char buf[MSG_SIZ], *engineName;
10132 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10133 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10134 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10136 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10137 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10138 appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10139 ParseArgsFromString(buf);
10145 char *recentEngines;
10148 RecentEngineEvent (int nr)
10151 // SwapEngines(1); // bump first to second
10152 // ReplaceEngine(&second, 1); // and load it there
10153 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10154 n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10155 if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10156 ReplaceEngine(&first, 0);
10157 FloatToFront(&appData.recentEngineList, command[n]);
10162 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10163 { // determine players from game number
10164 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10166 if(appData.tourneyType == 0) {
10167 roundsPerCycle = (nPlayers - 1) | 1;
10168 pairingsPerRound = nPlayers / 2;
10169 } else if(appData.tourneyType > 0) {
10170 roundsPerCycle = nPlayers - appData.tourneyType;
10171 pairingsPerRound = appData.tourneyType;
10173 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10174 gamesPerCycle = gamesPerRound * roundsPerCycle;
10175 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10176 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10177 curRound = nr / gamesPerRound; nr %= gamesPerRound;
10178 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10179 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10180 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10182 if(appData.cycleSync) *syncInterval = gamesPerCycle;
10183 if(appData.roundSync) *syncInterval = gamesPerRound;
10185 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10187 if(appData.tourneyType == 0) {
10188 if(curPairing == (nPlayers-1)/2 ) {
10189 *whitePlayer = curRound;
10190 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10192 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10193 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10194 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10195 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10197 } else if(appData.tourneyType > 1) {
10198 *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10199 *whitePlayer = curRound + appData.tourneyType;
10200 } else if(appData.tourneyType > 0) {
10201 *whitePlayer = curPairing;
10202 *blackPlayer = curRound + appData.tourneyType;
10205 // take care of white/black alternation per round.
10206 // For cycles and games this is already taken care of by default, derived from matchGame!
10207 return curRound & 1;
10211 NextTourneyGame (int nr, int *swapColors)
10212 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10214 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10216 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10217 tf = fopen(appData.tourneyFile, "r");
10218 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10219 ParseArgsFromFile(tf); fclose(tf);
10220 InitTimeControls(); // TC might be altered from tourney file
10222 nPlayers = CountPlayers(appData.participants); // count participants
10223 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10224 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10227 p = q = appData.results;
10228 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10229 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10230 DisplayMessage(_("Waiting for other game(s)"),"");
10231 waitingForGame = TRUE;
10232 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10235 waitingForGame = FALSE;
10238 if(appData.tourneyType < 0) {
10239 if(nr>=0 && !pairingReceived) {
10241 if(pairing.pr == NoProc) {
10242 if(!appData.pairingEngine[0]) {
10243 DisplayFatalError(_("No pairing engine specified"), 0, 1);
10246 StartChessProgram(&pairing); // starts the pairing engine
10248 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10249 SendToProgram(buf, &pairing);
10250 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10251 SendToProgram(buf, &pairing);
10252 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10254 pairingReceived = 0; // ... so we continue here
10256 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10257 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10258 matchGame = 1; roundNr = nr / syncInterval + 1;
10261 if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10263 // redefine engines, engine dir, etc.
10264 NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10265 if(first.pr == NoProc) {
10266 SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10267 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
10269 if(second.pr == NoProc) {
10271 SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10272 SwapEngines(1); // and make that valid for second engine by swapping
10273 InitEngine(&second, 1);
10275 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
10276 UpdateLogos(FALSE); // leave display to ModeHiglight()
10282 { // performs game initialization that does not invoke engines, and then tries to start the game
10283 int res, firstWhite, swapColors = 0;
10284 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10285 if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
10287 snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10288 if(strcmp(buf, currentDebugFile)) { // name has changed
10289 FILE *f = fopen(buf, "w");
10290 if(f) { // if opening the new file failed, just keep using the old one
10291 ASSIGN(currentDebugFile, buf);
10295 if(appData.serverFileName) {
10296 if(serverFP) fclose(serverFP);
10297 serverFP = fopen(appData.serverFileName, "w");
10298 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10299 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10303 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10304 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10305 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
10306 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10307 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10308 if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10309 Reset(FALSE, first.pr != NoProc);
10310 res = LoadGameOrPosition(matchGame); // setup game
10311 appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10312 if(!res) return; // abort when bad game/pos file
10313 TwoMachinesEvent();
10317 UserAdjudicationEvent (int result)
10319 ChessMove gameResult = GameIsDrawn;
10322 gameResult = WhiteWins;
10324 else if( result < 0 ) {
10325 gameResult = BlackWins;
10328 if( gameMode == TwoMachinesPlay ) {
10329 GameEnds( gameResult, "User adjudication", GE_XBOARD );
10334 // [HGM] save: calculate checksum of game to make games easily identifiable
10336 StringCheckSum (char *s)
10339 if(s==NULL) return 0;
10340 while(*s) i = i*259 + *s++;
10348 for(i=backwardMostMove; i<forwardMostMove; i++) {
10349 sum += pvInfoList[i].depth;
10350 sum += StringCheckSum(parseList[i]);
10351 sum += StringCheckSum(commentList[i]);
10354 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10355 return sum + StringCheckSum(commentList[i]);
10356 } // end of save patch
10359 GameEnds (ChessMove result, char *resultDetails, int whosays)
10361 GameMode nextGameMode;
10363 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10365 if(endingGame) return; /* [HGM] crash: forbid recursion */
10367 if(twoBoards) { // [HGM] dual: switch back to one board
10368 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10369 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10371 if (appData.debugMode) {
10372 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10373 result, resultDetails ? resultDetails : "(null)", whosays);
10376 fromX = fromY = -1; // [HGM] abort any move the user is entering.
10378 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10379 /* If we are playing on ICS, the server decides when the
10380 game is over, but the engine can offer to draw, claim
10384 if (appData.zippyPlay && first.initDone) {
10385 if (result == GameIsDrawn) {
10386 /* In case draw still needs to be claimed */
10387 SendToICS(ics_prefix);
10388 SendToICS("draw\n");
10389 } else if (StrCaseStr(resultDetails, "resign")) {
10390 SendToICS(ics_prefix);
10391 SendToICS("resign\n");
10395 endingGame = 0; /* [HGM] crash */
10399 /* If we're loading the game from a file, stop */
10400 if (whosays == GE_FILE) {
10401 (void) StopLoadGameTimer();
10405 /* Cancel draw offers */
10406 first.offeredDraw = second.offeredDraw = 0;
10408 /* If this is an ICS game, only ICS can really say it's done;
10409 if not, anyone can. */
10410 isIcsGame = (gameMode == IcsPlayingWhite ||
10411 gameMode == IcsPlayingBlack ||
10412 gameMode == IcsObserving ||
10413 gameMode == IcsExamining);
10415 if (!isIcsGame || whosays == GE_ICS) {
10416 /* OK -- not an ICS game, or ICS said it was done */
10418 if (!isIcsGame && !appData.noChessProgram)
10419 SetUserThinkingEnables();
10421 /* [HGM] if a machine claims the game end we verify this claim */
10422 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10423 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10425 ChessMove trueResult = (ChessMove) -1;
10427 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10428 first.twoMachinesColor[0] :
10429 second.twoMachinesColor[0] ;
10431 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10432 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10433 /* [HGM] verify: engine mate claims accepted if they were flagged */
10434 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10436 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10437 /* [HGM] verify: engine mate claims accepted if they were flagged */
10438 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10440 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10441 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10444 // now verify win claims, but not in drop games, as we don't understand those yet
10445 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10446 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10447 (result == WhiteWins && claimer == 'w' ||
10448 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10449 if (appData.debugMode) {
10450 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10451 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10453 if(result != trueResult) {
10454 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10455 result = claimer == 'w' ? BlackWins : WhiteWins;
10456 resultDetails = buf;
10459 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10460 && (forwardMostMove <= backwardMostMove ||
10461 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10462 (claimer=='b')==(forwardMostMove&1))
10464 /* [HGM] verify: draws that were not flagged are false claims */
10465 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10466 result = claimer == 'w' ? BlackWins : WhiteWins;
10467 resultDetails = buf;
10469 /* (Claiming a loss is accepted no questions asked!) */
10471 /* [HGM] bare: don't allow bare King to win */
10472 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10473 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10474 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10475 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10476 && result != GameIsDrawn)
10477 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10478 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10479 int p = (signed char)boards[forwardMostMove][i][j] - color;
10480 if(p >= 0 && p <= (int)WhiteKing) k++;
10482 if (appData.debugMode) {
10483 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10484 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10487 result = GameIsDrawn;
10488 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10489 resultDetails = buf;
10495 if(serverMoves != NULL && !loadFlag) { char c = '=';
10496 if(result==WhiteWins) c = '+';
10497 if(result==BlackWins) c = '-';
10498 if(resultDetails != NULL)
10499 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10501 if (resultDetails != NULL) {
10502 gameInfo.result = result;
10503 gameInfo.resultDetails = StrSave(resultDetails);
10505 /* display last move only if game was not loaded from file */
10506 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10507 DisplayMove(currentMove - 1);
10509 if (forwardMostMove != 0) {
10510 if (gameMode != PlayFromGameFile && gameMode != EditGame
10511 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10513 if (*appData.saveGameFile != NULLCHAR) {
10514 SaveGameToFile(appData.saveGameFile, TRUE);
10515 } else if (appData.autoSaveGames) {
10518 if (*appData.savePositionFile != NULLCHAR) {
10519 SavePositionToFile(appData.savePositionFile);
10524 /* Tell program how game ended in case it is learning */
10525 /* [HGM] Moved this to after saving the PGN, just in case */
10526 /* engine died and we got here through time loss. In that */
10527 /* case we will get a fatal error writing the pipe, which */
10528 /* would otherwise lose us the PGN. */
10529 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10530 /* output during GameEnds should never be fatal anymore */
10531 if (gameMode == MachinePlaysWhite ||
10532 gameMode == MachinePlaysBlack ||
10533 gameMode == TwoMachinesPlay ||
10534 gameMode == IcsPlayingWhite ||
10535 gameMode == IcsPlayingBlack ||
10536 gameMode == BeginningOfGame) {
10538 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10540 if (first.pr != NoProc) {
10541 SendToProgram(buf, &first);
10543 if (second.pr != NoProc &&
10544 gameMode == TwoMachinesPlay) {
10545 SendToProgram(buf, &second);
10550 if (appData.icsActive) {
10551 if (appData.quietPlay &&
10552 (gameMode == IcsPlayingWhite ||
10553 gameMode == IcsPlayingBlack)) {
10554 SendToICS(ics_prefix);
10555 SendToICS("set shout 1\n");
10557 nextGameMode = IcsIdle;
10558 ics_user_moved = FALSE;
10559 /* clean up premove. It's ugly when the game has ended and the
10560 * premove highlights are still on the board.
10563 gotPremove = FALSE;
10564 ClearPremoveHighlights();
10565 DrawPosition(FALSE, boards[currentMove]);
10567 if (whosays == GE_ICS) {
10570 if (gameMode == IcsPlayingWhite)
10572 else if(gameMode == IcsPlayingBlack)
10573 PlayIcsLossSound();
10576 if (gameMode == IcsPlayingBlack)
10578 else if(gameMode == IcsPlayingWhite)
10579 PlayIcsLossSound();
10582 PlayIcsDrawSound();
10585 PlayIcsUnfinishedSound();
10588 } else if (gameMode == EditGame ||
10589 gameMode == PlayFromGameFile ||
10590 gameMode == AnalyzeMode ||
10591 gameMode == AnalyzeFile) {
10592 nextGameMode = gameMode;
10594 nextGameMode = EndOfGame;
10599 nextGameMode = gameMode;
10602 if (appData.noChessProgram) {
10603 gameMode = nextGameMode;
10605 endingGame = 0; /* [HGM] crash */
10610 /* Put first chess program into idle state */
10611 if (first.pr != NoProc &&
10612 (gameMode == MachinePlaysWhite ||
10613 gameMode == MachinePlaysBlack ||
10614 gameMode == TwoMachinesPlay ||
10615 gameMode == IcsPlayingWhite ||
10616 gameMode == IcsPlayingBlack ||
10617 gameMode == BeginningOfGame)) {
10618 SendToProgram("force\n", &first);
10619 if (first.usePing) {
10621 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10622 SendToProgram(buf, &first);
10625 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10626 /* Kill off first chess program */
10627 if (first.isr != NULL)
10628 RemoveInputSource(first.isr);
10631 if (first.pr != NoProc) {
10633 DoSleep( appData.delayBeforeQuit );
10634 SendToProgram("quit\n", &first);
10635 DoSleep( appData.delayAfterQuit );
10636 DestroyChildProcess(first.pr, first.useSigterm);
10640 if (second.reuse) {
10641 /* Put second chess program into idle state */
10642 if (second.pr != NoProc &&
10643 gameMode == TwoMachinesPlay) {
10644 SendToProgram("force\n", &second);
10645 if (second.usePing) {
10647 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10648 SendToProgram(buf, &second);
10651 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10652 /* Kill off second chess program */
10653 if (second.isr != NULL)
10654 RemoveInputSource(second.isr);
10657 if (second.pr != NoProc) {
10658 DoSleep( appData.delayBeforeQuit );
10659 SendToProgram("quit\n", &second);
10660 DoSleep( appData.delayAfterQuit );
10661 DestroyChildProcess(second.pr, second.useSigterm);
10663 second.pr = NoProc;
10666 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10667 char resChar = '=';
10671 if (first.twoMachinesColor[0] == 'w') {
10674 second.matchWins++;
10679 if (first.twoMachinesColor[0] == 'b') {
10682 second.matchWins++;
10685 case GameUnfinished:
10691 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10692 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10693 if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10694 ReserveGame(nextGame, resChar); // sets nextGame
10695 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10696 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10697 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10699 if (nextGame <= appData.matchGames && !abortMatch) {
10700 gameMode = nextGameMode;
10701 matchGame = nextGame; // this will be overruled in tourney mode!
10702 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10703 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10704 endingGame = 0; /* [HGM] crash */
10707 gameMode = nextGameMode;
10708 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10709 first.tidy, second.tidy,
10710 first.matchWins, second.matchWins,
10711 appData.matchGames - (first.matchWins + second.matchWins));
10712 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10713 if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10714 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10715 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10716 first.twoMachinesColor = "black\n";
10717 second.twoMachinesColor = "white\n";
10719 first.twoMachinesColor = "white\n";
10720 second.twoMachinesColor = "black\n";
10724 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10725 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10727 gameMode = nextGameMode;
10729 endingGame = 0; /* [HGM] crash */
10730 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10731 if(matchMode == TRUE) { // match through command line: exit with or without popup
10733 ToNrEvent(forwardMostMove);
10734 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10736 } else DisplayFatalError(buf, 0, 0);
10737 } else { // match through menu; just stop, with or without popup
10738 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10741 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10742 } else DisplayNote(buf);
10744 if(ranking) free(ranking);
10748 /* Assumes program was just initialized (initString sent).
10749 Leaves program in force mode. */
10751 FeedMovesToProgram (ChessProgramState *cps, int upto)
10755 if (appData.debugMode)
10756 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10757 startedFromSetupPosition ? "position and " : "",
10758 backwardMostMove, upto, cps->which);
10759 if(currentlyInitializedVariant != gameInfo.variant) {
10761 // [HGM] variantswitch: make engine aware of new variant
10762 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10763 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10764 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10765 SendToProgram(buf, cps);
10766 currentlyInitializedVariant = gameInfo.variant;
10768 SendToProgram("force\n", cps);
10769 if (startedFromSetupPosition) {
10770 SendBoard(cps, backwardMostMove);
10771 if (appData.debugMode) {
10772 fprintf(debugFP, "feedMoves\n");
10775 for (i = backwardMostMove; i < upto; i++) {
10776 SendMoveToProgram(i, cps);
10782 ResurrectChessProgram ()
10784 /* The chess program may have exited.
10785 If so, restart it and feed it all the moves made so far. */
10786 static int doInit = 0;
10788 if (appData.noChessProgram) return 1;
10790 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10791 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10792 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10793 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10795 if (first.pr != NoProc) return 1;
10796 StartChessProgram(&first);
10798 InitChessProgram(&first, FALSE);
10799 FeedMovesToProgram(&first, currentMove);
10801 if (!first.sendTime) {
10802 /* can't tell gnuchess what its clock should read,
10803 so we bow to its notion. */
10805 timeRemaining[0][currentMove] = whiteTimeRemaining;
10806 timeRemaining[1][currentMove] = blackTimeRemaining;
10809 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10810 appData.icsEngineAnalyze) && first.analysisSupport) {
10811 SendToProgram("analyze\n", &first);
10812 first.analyzing = TRUE;
10818 * Button procedures
10821 Reset (int redraw, int init)
10825 if (appData.debugMode) {
10826 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10827 redraw, init, gameMode);
10829 CleanupTail(); // [HGM] vari: delete any stored variations
10830 CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10831 pausing = pauseExamInvalid = FALSE;
10832 startedFromSetupPosition = blackPlaysFirst = FALSE;
10834 whiteFlag = blackFlag = FALSE;
10835 userOfferedDraw = FALSE;
10836 hintRequested = bookRequested = FALSE;
10837 first.maybeThinking = FALSE;
10838 second.maybeThinking = FALSE;
10839 first.bookSuspend = FALSE; // [HGM] book
10840 second.bookSuspend = FALSE;
10841 thinkOutput[0] = NULLCHAR;
10842 lastHint[0] = NULLCHAR;
10843 ClearGameInfo(&gameInfo);
10844 gameInfo.variant = StringToVariant(appData.variant);
10845 ics_user_moved = ics_clock_paused = FALSE;
10846 ics_getting_history = H_FALSE;
10848 white_holding[0] = black_holding[0] = NULLCHAR;
10849 ClearProgramStats();
10850 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10854 flipView = appData.flipView;
10855 ClearPremoveHighlights();
10856 gotPremove = FALSE;
10857 alarmSounded = FALSE;
10859 GameEnds(EndOfFile, NULL, GE_PLAYER);
10860 if(appData.serverMovesName != NULL) {
10861 /* [HGM] prepare to make moves file for broadcasting */
10862 clock_t t = clock();
10863 if(serverMoves != NULL) fclose(serverMoves);
10864 serverMoves = fopen(appData.serverMovesName, "r");
10865 if(serverMoves != NULL) {
10866 fclose(serverMoves);
10867 /* delay 15 sec before overwriting, so all clients can see end */
10868 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10870 serverMoves = fopen(appData.serverMovesName, "w");
10874 gameMode = BeginningOfGame;
10876 if(appData.icsActive) gameInfo.variant = VariantNormal;
10877 currentMove = forwardMostMove = backwardMostMove = 0;
10878 MarkTargetSquares(1);
10879 InitPosition(redraw);
10880 for (i = 0; i < MAX_MOVES; i++) {
10881 if (commentList[i] != NULL) {
10882 free(commentList[i]);
10883 commentList[i] = NULL;
10887 timeRemaining[0][0] = whiteTimeRemaining;
10888 timeRemaining[1][0] = blackTimeRemaining;
10890 if (first.pr == NoProc) {
10891 StartChessProgram(&first);
10894 InitChessProgram(&first, startedFromSetupPosition);
10897 DisplayMessage("", "");
10898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10900 ClearMap(); // [HGM] exclude: invalidate map
10904 AutoPlayGameLoop ()
10907 if (!AutoPlayOneMove())
10909 if (matchMode || appData.timeDelay == 0)
10911 if (appData.timeDelay < 0)
10913 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10922 int fromX, fromY, toX, toY;
10924 if (appData.debugMode) {
10925 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10928 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10931 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10932 pvInfoList[currentMove].depth = programStats.depth;
10933 pvInfoList[currentMove].score = programStats.score;
10934 pvInfoList[currentMove].time = 0;
10935 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10938 if (currentMove >= forwardMostMove) {
10939 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10940 // gameMode = EndOfGame;
10941 // ModeHighlight();
10943 /* [AS] Clear current move marker at the end of a game */
10944 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10949 toX = moveList[currentMove][2] - AAA;
10950 toY = moveList[currentMove][3] - ONE;
10952 if (moveList[currentMove][1] == '@') {
10953 if (appData.highlightLastMove) {
10954 SetHighlights(-1, -1, toX, toY);
10957 fromX = moveList[currentMove][0] - AAA;
10958 fromY = moveList[currentMove][1] - ONE;
10960 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10962 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10964 if (appData.highlightLastMove) {
10965 SetHighlights(fromX, fromY, toX, toY);
10968 DisplayMove(currentMove);
10969 SendMoveToProgram(currentMove++, &first);
10970 DisplayBothClocks();
10971 DrawPosition(FALSE, boards[currentMove]);
10972 // [HGM] PV info: always display, routine tests if empty
10973 DisplayComment(currentMove - 1, commentList[currentMove]);
10979 LoadGameOneMove (ChessMove readAhead)
10981 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10982 char promoChar = NULLCHAR;
10983 ChessMove moveType;
10984 char move[MSG_SIZ];
10987 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10988 gameMode != AnalyzeMode && gameMode != Training) {
10993 yyboardindex = forwardMostMove;
10994 if (readAhead != EndOfFile) {
10995 moveType = readAhead;
10997 if (gameFileFP == NULL)
10999 moveType = (ChessMove) Myylex();
11003 switch (moveType) {
11005 if (appData.debugMode)
11006 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11009 /* append the comment but don't display it */
11010 AppendComment(currentMove, p, FALSE);
11013 case WhiteCapturesEnPassant:
11014 case BlackCapturesEnPassant:
11015 case WhitePromotion:
11016 case BlackPromotion:
11017 case WhiteNonPromotion:
11018 case BlackNonPromotion:
11020 case WhiteKingSideCastle:
11021 case WhiteQueenSideCastle:
11022 case BlackKingSideCastle:
11023 case BlackQueenSideCastle:
11024 case WhiteKingSideCastleWild:
11025 case WhiteQueenSideCastleWild:
11026 case BlackKingSideCastleWild:
11027 case BlackQueenSideCastleWild:
11029 case WhiteHSideCastleFR:
11030 case WhiteASideCastleFR:
11031 case BlackHSideCastleFR:
11032 case BlackASideCastleFR:
11034 if (appData.debugMode)
11035 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11036 fromX = currentMoveString[0] - AAA;
11037 fromY = currentMoveString[1] - ONE;
11038 toX = currentMoveString[2] - AAA;
11039 toY = currentMoveString[3] - ONE;
11040 promoChar = currentMoveString[4];
11045 if (appData.debugMode)
11046 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11047 fromX = moveType == WhiteDrop ?
11048 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11049 (int) CharToPiece(ToLower(currentMoveString[0]));
11051 toX = currentMoveString[2] - AAA;
11052 toY = currentMoveString[3] - ONE;
11058 case GameUnfinished:
11059 if (appData.debugMode)
11060 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11061 p = strchr(yy_text, '{');
11062 if (p == NULL) p = strchr(yy_text, '(');
11065 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11067 q = strchr(p, *p == '{' ? '}' : ')');
11068 if (q != NULL) *q = NULLCHAR;
11071 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11072 GameEnds(moveType, p, GE_FILE);
11074 if (cmailMsgLoaded) {
11076 flipView = WhiteOnMove(currentMove);
11077 if (moveType == GameUnfinished) flipView = !flipView;
11078 if (appData.debugMode)
11079 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11084 if (appData.debugMode)
11085 fprintf(debugFP, "Parser hit end of file\n");
11086 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11092 if (WhiteOnMove(currentMove)) {
11093 GameEnds(BlackWins, "Black mates", GE_FILE);
11095 GameEnds(WhiteWins, "White mates", GE_FILE);
11099 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11105 case MoveNumberOne:
11106 if (lastLoadGameStart == GNUChessGame) {
11107 /* GNUChessGames have numbers, but they aren't move numbers */
11108 if (appData.debugMode)
11109 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11110 yy_text, (int) moveType);
11111 return LoadGameOneMove(EndOfFile); /* tail recursion */
11113 /* else fall thru */
11118 /* Reached start of next game in file */
11119 if (appData.debugMode)
11120 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11121 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11127 if (WhiteOnMove(currentMove)) {
11128 GameEnds(BlackWins, "Black mates", GE_FILE);
11130 GameEnds(WhiteWins, "White mates", GE_FILE);
11134 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11140 case PositionDiagram: /* should not happen; ignore */
11141 case ElapsedTime: /* ignore */
11142 case NAG: /* ignore */
11143 if (appData.debugMode)
11144 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11145 yy_text, (int) moveType);
11146 return LoadGameOneMove(EndOfFile); /* tail recursion */
11149 if (appData.testLegality) {
11150 if (appData.debugMode)
11151 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11152 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11153 (forwardMostMove / 2) + 1,
11154 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11155 DisplayError(move, 0);
11158 if (appData.debugMode)
11159 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11160 yy_text, currentMoveString);
11161 fromX = currentMoveString[0] - AAA;
11162 fromY = currentMoveString[1] - ONE;
11163 toX = currentMoveString[2] - AAA;
11164 toY = currentMoveString[3] - ONE;
11165 promoChar = currentMoveString[4];
11169 case AmbiguousMove:
11170 if (appData.debugMode)
11171 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11172 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11173 (forwardMostMove / 2) + 1,
11174 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11175 DisplayError(move, 0);
11180 case ImpossibleMove:
11181 if (appData.debugMode)
11182 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11183 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11184 (forwardMostMove / 2) + 1,
11185 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11186 DisplayError(move, 0);
11192 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11193 DrawPosition(FALSE, boards[currentMove]);
11194 DisplayBothClocks();
11195 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11196 DisplayComment(currentMove - 1, commentList[currentMove]);
11198 (void) StopLoadGameTimer();
11200 cmailOldMove = forwardMostMove;
11203 /* currentMoveString is set as a side-effect of yylex */
11205 thinkOutput[0] = NULLCHAR;
11206 MakeMove(fromX, fromY, toX, toY, promoChar);
11207 currentMove = forwardMostMove;
11212 /* Load the nth game from the given file */
11214 LoadGameFromFile (char *filename, int n, char *title, int useList)
11219 if (strcmp(filename, "-") == 0) {
11223 f = fopen(filename, "rb");
11225 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11226 DisplayError(buf, errno);
11230 if (fseek(f, 0, 0) == -1) {
11231 /* f is not seekable; probably a pipe */
11234 if (useList && n == 0) {
11235 int error = GameListBuild(f);
11237 DisplayError(_("Cannot build game list"), error);
11238 } else if (!ListEmpty(&gameList) &&
11239 ((ListGame *) gameList.tailPred)->number > 1) {
11240 GameListPopUp(f, title);
11247 return LoadGame(f, n, title, FALSE);
11252 MakeRegisteredMove ()
11254 int fromX, fromY, toX, toY;
11256 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11257 switch (cmailMoveType[lastLoadGameNumber - 1]) {
11260 if (appData.debugMode)
11261 fprintf(debugFP, "Restoring %s for game %d\n",
11262 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11264 thinkOutput[0] = NULLCHAR;
11265 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11266 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11267 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11268 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11269 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11270 promoChar = cmailMove[lastLoadGameNumber - 1][4];
11271 MakeMove(fromX, fromY, toX, toY, promoChar);
11272 ShowMove(fromX, fromY, toX, toY);
11274 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11281 if (WhiteOnMove(currentMove)) {
11282 GameEnds(BlackWins, "Black mates", GE_PLAYER);
11284 GameEnds(WhiteWins, "White mates", GE_PLAYER);
11289 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11296 if (WhiteOnMove(currentMove)) {
11297 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11299 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11304 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11315 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11317 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11321 if (gameNumber > nCmailGames) {
11322 DisplayError(_("No more games in this message"), 0);
11325 if (f == lastLoadGameFP) {
11326 int offset = gameNumber - lastLoadGameNumber;
11328 cmailMsg[0] = NULLCHAR;
11329 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11330 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11331 nCmailMovesRegistered--;
11333 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11334 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11335 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11338 if (! RegisterMove()) return FALSE;
11342 retVal = LoadGame(f, gameNumber, title, useList);
11344 /* Make move registered during previous look at this game, if any */
11345 MakeRegisteredMove();
11347 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11348 commentList[currentMove]
11349 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11350 DisplayComment(currentMove - 1, commentList[currentMove]);
11356 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11358 ReloadGame (int offset)
11360 int gameNumber = lastLoadGameNumber + offset;
11361 if (lastLoadGameFP == NULL) {
11362 DisplayError(_("No game has been loaded yet"), 0);
11365 if (gameNumber <= 0) {
11366 DisplayError(_("Can't back up any further"), 0);
11369 if (cmailMsgLoaded) {
11370 return CmailLoadGame(lastLoadGameFP, gameNumber,
11371 lastLoadGameTitle, lastLoadGameUseList);
11373 return LoadGame(lastLoadGameFP, gameNumber,
11374 lastLoadGameTitle, lastLoadGameUseList);
11378 int keys[EmptySquare+1];
11381 PositionMatches (Board b1, Board b2)
11384 switch(appData.searchMode) {
11385 case 1: return CompareWithRights(b1, b2);
11387 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11388 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11392 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11393 if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11394 sum += keys[b1[r][f]] - keys[b2[r][f]];
11398 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11399 sum += keys[b1[r][f]] - keys[b2[r][f]];
11411 int pieceList[256], quickBoard[256];
11412 ChessSquare pieceType[256] = { EmptySquare };
11413 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11414 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11415 int soughtTotal, turn;
11416 Boolean epOK, flipSearch;
11419 unsigned char piece, to;
11422 #define DSIZE (250000)
11424 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11425 Move *moveDatabase = initialSpace;
11426 unsigned int movePtr, dataSize = DSIZE;
11429 MakePieceList (Board board, int *counts)
11431 int r, f, n=Q_PROMO, total=0;
11432 for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11433 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11434 int sq = f + (r<<4);
11435 if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11436 quickBoard[sq] = ++n;
11438 pieceType[n] = board[r][f];
11439 counts[board[r][f]]++;
11440 if(board[r][f] == WhiteKing) pieceList[1] = n; else
11441 if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11445 epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11450 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11452 int sq = fromX + (fromY<<4);
11453 int piece = quickBoard[sq];
11454 quickBoard[sq] = 0;
11455 moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11456 if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11457 int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11458 moveDatabase[movePtr++].piece = Q_WCASTL;
11459 quickBoard[sq] = piece;
11460 piece = quickBoard[from]; quickBoard[from] = 0;
11461 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11463 if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11464 int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11465 moveDatabase[movePtr++].piece = Q_BCASTL;
11466 quickBoard[sq] = piece;
11467 piece = quickBoard[from]; quickBoard[from] = 0;
11468 moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11470 if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11471 quickBoard[(fromY<<4)+toX] = 0;
11472 moveDatabase[movePtr].piece = Q_EP;
11473 moveDatabase[movePtr++].to = (fromY<<4)+toX;
11474 moveDatabase[movePtr].to = sq;
11476 if(promoPiece != pieceType[piece]) {
11477 moveDatabase[movePtr++].piece = Q_PROMO;
11478 moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11480 moveDatabase[movePtr].piece = piece;
11481 quickBoard[sq] = piece;
11486 PackGame (Board board)
11488 Move *newSpace = NULL;
11489 moveDatabase[movePtr].piece = 0; // terminate previous game
11490 if(movePtr > dataSize) {
11491 if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11492 dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11493 if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11496 Move *p = moveDatabase, *q = newSpace;
11497 for(i=0; i<movePtr; i++) *q++ = *p++; // copy to newly allocated space
11498 if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11499 moveDatabase = newSpace;
11500 } else { // calloc failed, we must be out of memory. Too bad...
11501 dataSize = 0; // prevent calloc events for all subsequent games
11502 return 0; // and signal this one isn't cached
11506 MakePieceList(board, counts);
11511 QuickCompare (Board board, int *minCounts, int *maxCounts)
11512 { // compare according to search mode
11514 switch(appData.searchMode)
11516 case 1: // exact position match
11517 if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11518 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11519 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11522 case 2: // can have extra material on empty squares
11523 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11524 if(board[r][f] == EmptySquare) continue;
11525 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11528 case 3: // material with exact Pawn structure
11529 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11530 if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11531 if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11532 } // fall through to material comparison
11533 case 4: // exact material
11534 for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11536 case 6: // material range with given imbalance
11537 for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11538 // fall through to range comparison
11539 case 5: // material range
11540 for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11546 QuickScan (Board board, Move *move)
11547 { // reconstruct game,and compare all positions in it
11548 int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1;
11550 int piece = move->piece;
11551 int to = move->to, from = pieceList[piece];
11552 if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11553 if(!piece) return -1;
11554 if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11555 piece = (++move)->piece;
11556 from = pieceList[piece];
11557 counts[pieceType[piece]]--;
11558 pieceType[piece] = (ChessSquare) move->to;
11559 counts[move->to]++;
11560 } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11561 counts[pieceType[quickBoard[to]]]--;
11562 quickBoard[to] = 0; total--;
11565 } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11567 piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11568 from = pieceList[piece]; // so this must be King
11569 quickBoard[from] = 0;
11570 pieceList[piece] = to;
11571 from = pieceList[(++move)->piece]; // for FRC this has to be done here
11572 quickBoard[from] = 0; // rook
11573 quickBoard[to] = piece;
11574 to = move->to; piece = move->piece;
11578 if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11579 if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11580 quickBoard[from] = 0;
11582 quickBoard[to] = piece;
11583 pieceList[piece] = to;
11585 if(QuickCompare(soughtBoard, minSought, maxSought) ||
11586 appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11587 flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11588 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11590 static int lastCounts[EmptySquare+1];
11592 if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11593 if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11594 } else stretch = 0;
11595 if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11596 move++; delayedKing = -1;
11604 flipSearch = FALSE;
11605 CopyBoard(soughtBoard, boards[currentMove]);
11606 soughtTotal = MakePieceList(soughtBoard, maxSought);
11607 soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11608 if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11609 CopyBoard(reverseBoard, boards[currentMove]);
11610 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11611 int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11612 if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11613 reverseBoard[r][f] = piece;
11615 reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11616 for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11617 if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11618 || (boards[currentMove][CASTLING][2] == NoRights ||
11619 boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11620 && (boards[currentMove][CASTLING][5] == NoRights ||
11621 boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11624 CopyBoard(flipBoard, soughtBoard);
11625 CopyBoard(rotateBoard, reverseBoard);
11626 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11627 flipBoard[r][f] = soughtBoard[r][BOARD_WIDTH-1-f];
11628 rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11631 for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11632 if(appData.searchMode >= 5) {
11633 for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11634 MakePieceList(soughtBoard, minSought);
11635 for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11637 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11638 soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11641 GameInfo dummyInfo;
11644 GameContainsPosition (FILE *f, ListGame *lg)
11646 int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11647 int fromX, fromY, toX, toY;
11649 static int initDone=FALSE;
11651 // weed out games based on numerical tag comparison
11652 if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11653 if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11654 if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11655 if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11657 for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11660 if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11661 else CopyBoard(boards[scratch], initialPosition); // default start position
11664 if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11665 if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11668 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11669 fseek(f, lg->offset, 0);
11672 yyboardindex = scratch;
11673 quickFlag = plyNr+1;
11678 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11684 if(plyNr) return -1; // after we have seen moves, this is for new game
11687 case AmbiguousMove: // we cannot reconstruct the game beyond these two
11688 case ImpossibleMove:
11689 case WhiteWins: // game ends here with these four
11692 case GameUnfinished:
11696 if(appData.testLegality) return -1;
11697 case WhiteCapturesEnPassant:
11698 case BlackCapturesEnPassant:
11699 case WhitePromotion:
11700 case BlackPromotion:
11701 case WhiteNonPromotion:
11702 case BlackNonPromotion:
11704 case WhiteKingSideCastle:
11705 case WhiteQueenSideCastle:
11706 case BlackKingSideCastle:
11707 case BlackQueenSideCastle:
11708 case WhiteKingSideCastleWild:
11709 case WhiteQueenSideCastleWild:
11710 case BlackKingSideCastleWild:
11711 case BlackQueenSideCastleWild:
11712 case WhiteHSideCastleFR:
11713 case WhiteASideCastleFR:
11714 case BlackHSideCastleFR:
11715 case BlackASideCastleFR:
11716 fromX = currentMoveString[0] - AAA;
11717 fromY = currentMoveString[1] - ONE;
11718 toX = currentMoveString[2] - AAA;
11719 toY = currentMoveString[3] - ONE;
11720 promoChar = currentMoveString[4];
11724 fromX = next == WhiteDrop ?
11725 (int) CharToPiece(ToUpper(currentMoveString[0])) :
11726 (int) CharToPiece(ToLower(currentMoveString[0]));
11728 toX = currentMoveString[2] - AAA;
11729 toY = currentMoveString[3] - ONE;
11733 // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11735 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11736 if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11737 if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11738 if(appData.findMirror) {
11739 if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11740 if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11745 /* Load the nth game from open file f */
11747 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11751 int gn = gameNumber;
11752 ListGame *lg = NULL;
11753 int numPGNTags = 0;
11755 GameMode oldGameMode;
11756 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11758 if (appData.debugMode)
11759 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11761 if (gameMode == Training )
11762 SetTrainingModeOff();
11764 oldGameMode = gameMode;
11765 if (gameMode != BeginningOfGame) {
11766 Reset(FALSE, TRUE);
11770 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11771 fclose(lastLoadGameFP);
11775 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11778 fseek(f, lg->offset, 0);
11779 GameListHighlight(gameNumber);
11780 pos = lg->position;
11784 DisplayError(_("Game number out of range"), 0);
11789 if (fseek(f, 0, 0) == -1) {
11790 if (f == lastLoadGameFP ?
11791 gameNumber == lastLoadGameNumber + 1 :
11795 DisplayError(_("Can't seek on game file"), 0);
11800 lastLoadGameFP = f;
11801 lastLoadGameNumber = gameNumber;
11802 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11803 lastLoadGameUseList = useList;
11807 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11808 snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11809 lg->gameInfo.black);
11811 } else if (*title != NULLCHAR) {
11812 if (gameNumber > 1) {
11813 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11816 DisplayTitle(title);
11820 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11821 gameMode = PlayFromGameFile;
11825 currentMove = forwardMostMove = backwardMostMove = 0;
11826 CopyBoard(boards[0], initialPosition);
11830 * Skip the first gn-1 games in the file.
11831 * Also skip over anything that precedes an identifiable
11832 * start of game marker, to avoid being confused by
11833 * garbage at the start of the file. Currently
11834 * recognized start of game markers are the move number "1",
11835 * the pattern "gnuchess .* game", the pattern
11836 * "^[#;%] [^ ]* game file", and a PGN tag block.
11837 * A game that starts with one of the latter two patterns
11838 * will also have a move number 1, possibly
11839 * following a position diagram.
11840 * 5-4-02: Let's try being more lenient and allowing a game to
11841 * start with an unnumbered move. Does that break anything?
11843 cm = lastLoadGameStart = EndOfFile;
11845 yyboardindex = forwardMostMove;
11846 cm = (ChessMove) Myylex();
11849 if (cmailMsgLoaded) {
11850 nCmailGames = CMAIL_MAX_GAMES - gn;
11853 DisplayError(_("Game not found in file"), 0);
11860 lastLoadGameStart = cm;
11863 case MoveNumberOne:
11864 switch (lastLoadGameStart) {
11869 case MoveNumberOne:
11871 gn--; /* count this game */
11872 lastLoadGameStart = cm;
11881 switch (lastLoadGameStart) {
11884 case MoveNumberOne:
11886 gn--; /* count this game */
11887 lastLoadGameStart = cm;
11890 lastLoadGameStart = cm; /* game counted already */
11898 yyboardindex = forwardMostMove;
11899 cm = (ChessMove) Myylex();
11900 } while (cm == PGNTag || cm == Comment);
11907 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11908 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11909 != CMAIL_OLD_RESULT) {
11911 cmailResult[ CMAIL_MAX_GAMES
11912 - gn - 1] = CMAIL_OLD_RESULT;
11918 /* Only a NormalMove can be at the start of a game
11919 * without a position diagram. */
11920 if (lastLoadGameStart == EndOfFile ) {
11922 lastLoadGameStart = MoveNumberOne;
11931 if (appData.debugMode)
11932 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11934 if (cm == XBoardGame) {
11935 /* Skip any header junk before position diagram and/or move 1 */
11937 yyboardindex = forwardMostMove;
11938 cm = (ChessMove) Myylex();
11940 if (cm == EndOfFile ||
11941 cm == GNUChessGame || cm == XBoardGame) {
11942 /* Empty game; pretend end-of-file and handle later */
11947 if (cm == MoveNumberOne || cm == PositionDiagram ||
11948 cm == PGNTag || cm == Comment)
11951 } else if (cm == GNUChessGame) {
11952 if (gameInfo.event != NULL) {
11953 free(gameInfo.event);
11955 gameInfo.event = StrSave(yy_text);
11958 startedFromSetupPosition = FALSE;
11959 while (cm == PGNTag) {
11960 if (appData.debugMode)
11961 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11962 err = ParsePGNTag(yy_text, &gameInfo);
11963 if (!err) numPGNTags++;
11965 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11966 if(gameInfo.variant != oldVariant) {
11967 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11968 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11969 InitPosition(TRUE);
11970 oldVariant = gameInfo.variant;
11971 if (appData.debugMode)
11972 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11976 if (gameInfo.fen != NULL) {
11977 Board initial_position;
11978 startedFromSetupPosition = TRUE;
11979 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11981 DisplayError(_("Bad FEN position in file"), 0);
11984 CopyBoard(boards[0], initial_position);
11985 if (blackPlaysFirst) {
11986 currentMove = forwardMostMove = backwardMostMove = 1;
11987 CopyBoard(boards[1], initial_position);
11988 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11989 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11990 timeRemaining[0][1] = whiteTimeRemaining;
11991 timeRemaining[1][1] = blackTimeRemaining;
11992 if (commentList[0] != NULL) {
11993 commentList[1] = commentList[0];
11994 commentList[0] = NULL;
11997 currentMove = forwardMostMove = backwardMostMove = 0;
11999 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12001 initialRulePlies = FENrulePlies;
12002 for( i=0; i< nrCastlingRights; i++ )
12003 initialRights[i] = initial_position[CASTLING][i];
12005 yyboardindex = forwardMostMove;
12006 free(gameInfo.fen);
12007 gameInfo.fen = NULL;
12010 yyboardindex = forwardMostMove;
12011 cm = (ChessMove) Myylex();
12013 /* Handle comments interspersed among the tags */
12014 while (cm == Comment) {
12016 if (appData.debugMode)
12017 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12019 AppendComment(currentMove, p, FALSE);
12020 yyboardindex = forwardMostMove;
12021 cm = (ChessMove) Myylex();
12025 /* don't rely on existence of Event tag since if game was
12026 * pasted from clipboard the Event tag may not exist
12028 if (numPGNTags > 0){
12030 if (gameInfo.variant == VariantNormal) {
12031 VariantClass v = StringToVariant(gameInfo.event);
12032 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12033 if(v < VariantShogi) gameInfo.variant = v;
12036 if( appData.autoDisplayTags ) {
12037 tags = PGNTags(&gameInfo);
12038 TagsPopUp(tags, CmailMsg());
12043 /* Make something up, but don't display it now */
12048 if (cm == PositionDiagram) {
12051 Board initial_position;
12053 if (appData.debugMode)
12054 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12056 if (!startedFromSetupPosition) {
12058 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12059 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12070 initial_position[i][j++] = CharToPiece(*p);
12073 while (*p == ' ' || *p == '\t' ||
12074 *p == '\n' || *p == '\r') p++;
12076 if (strncmp(p, "black", strlen("black"))==0)
12077 blackPlaysFirst = TRUE;
12079 blackPlaysFirst = FALSE;
12080 startedFromSetupPosition = TRUE;
12082 CopyBoard(boards[0], initial_position);
12083 if (blackPlaysFirst) {
12084 currentMove = forwardMostMove = backwardMostMove = 1;
12085 CopyBoard(boards[1], initial_position);
12086 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12087 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12088 timeRemaining[0][1] = whiteTimeRemaining;
12089 timeRemaining[1][1] = blackTimeRemaining;
12090 if (commentList[0] != NULL) {
12091 commentList[1] = commentList[0];
12092 commentList[0] = NULL;
12095 currentMove = forwardMostMove = backwardMostMove = 0;
12098 yyboardindex = forwardMostMove;
12099 cm = (ChessMove) Myylex();
12102 if (first.pr == NoProc) {
12103 StartChessProgram(&first);
12105 InitChessProgram(&first, FALSE);
12106 SendToProgram("force\n", &first);
12107 if (startedFromSetupPosition) {
12108 SendBoard(&first, forwardMostMove);
12109 if (appData.debugMode) {
12110 fprintf(debugFP, "Load Game\n");
12112 DisplayBothClocks();
12115 /* [HGM] server: flag to write setup moves in broadcast file as one */
12116 loadFlag = appData.suppressLoadMoves;
12118 while (cm == Comment) {
12120 if (appData.debugMode)
12121 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12123 AppendComment(currentMove, p, FALSE);
12124 yyboardindex = forwardMostMove;
12125 cm = (ChessMove) Myylex();
12128 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12129 cm == WhiteWins || cm == BlackWins ||
12130 cm == GameIsDrawn || cm == GameUnfinished) {
12131 DisplayMessage("", _("No moves in game"));
12132 if (cmailMsgLoaded) {
12133 if (appData.debugMode)
12134 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12138 DrawPosition(FALSE, boards[currentMove]);
12139 DisplayBothClocks();
12140 gameMode = EditGame;
12147 // [HGM] PV info: routine tests if comment empty
12148 if (!matchMode && (pausing || appData.timeDelay != 0)) {
12149 DisplayComment(currentMove - 1, commentList[currentMove]);
12151 if (!matchMode && appData.timeDelay != 0)
12152 DrawPosition(FALSE, boards[currentMove]);
12154 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12155 programStats.ok_to_send = 1;
12158 /* if the first token after the PGN tags is a move
12159 * and not move number 1, retrieve it from the parser
12161 if (cm != MoveNumberOne)
12162 LoadGameOneMove(cm);
12164 /* load the remaining moves from the file */
12165 while (LoadGameOneMove(EndOfFile)) {
12166 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12167 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12170 /* rewind to the start of the game */
12171 currentMove = backwardMostMove;
12173 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12175 if (oldGameMode == AnalyzeFile ||
12176 oldGameMode == AnalyzeMode) {
12177 AnalyzeFileEvent();
12180 if (!matchMode && pos > 0) {
12181 ToNrEvent(pos); // [HGM] no autoplay if selected on position
12183 if (matchMode || appData.timeDelay == 0) {
12185 } else if (appData.timeDelay > 0) {
12186 AutoPlayGameLoop();
12189 if (appData.debugMode)
12190 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12192 loadFlag = 0; /* [HGM] true game starts */
12196 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12198 ReloadPosition (int offset)
12200 int positionNumber = lastLoadPositionNumber + offset;
12201 if (lastLoadPositionFP == NULL) {
12202 DisplayError(_("No position has been loaded yet"), 0);
12205 if (positionNumber <= 0) {
12206 DisplayError(_("Can't back up any further"), 0);
12209 return LoadPosition(lastLoadPositionFP, positionNumber,
12210 lastLoadPositionTitle);
12213 /* Load the nth position from the given file */
12215 LoadPositionFromFile (char *filename, int n, char *title)
12220 if (strcmp(filename, "-") == 0) {
12221 return LoadPosition(stdin, n, "stdin");
12223 f = fopen(filename, "rb");
12225 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12226 DisplayError(buf, errno);
12229 return LoadPosition(f, n, title);
12234 /* Load the nth position from the given open file, and close it */
12236 LoadPosition (FILE *f, int positionNumber, char *title)
12238 char *p, line[MSG_SIZ];
12239 Board initial_position;
12240 int i, j, fenMode, pn;
12242 if (gameMode == Training )
12243 SetTrainingModeOff();
12245 if (gameMode != BeginningOfGame) {
12246 Reset(FALSE, TRUE);
12248 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12249 fclose(lastLoadPositionFP);
12251 if (positionNumber == 0) positionNumber = 1;
12252 lastLoadPositionFP = f;
12253 lastLoadPositionNumber = positionNumber;
12254 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12255 if (first.pr == NoProc && !appData.noChessProgram) {
12256 StartChessProgram(&first);
12257 InitChessProgram(&first, FALSE);
12259 pn = positionNumber;
12260 if (positionNumber < 0) {
12261 /* Negative position number means to seek to that byte offset */
12262 if (fseek(f, -positionNumber, 0) == -1) {
12263 DisplayError(_("Can't seek on position file"), 0);
12268 if (fseek(f, 0, 0) == -1) {
12269 if (f == lastLoadPositionFP ?
12270 positionNumber == lastLoadPositionNumber + 1 :
12271 positionNumber == 1) {
12274 DisplayError(_("Can't seek on position file"), 0);
12279 /* See if this file is FEN or old-style xboard */
12280 if (fgets(line, MSG_SIZ, f) == NULL) {
12281 DisplayError(_("Position not found in file"), 0);
12284 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12285 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12288 if (fenMode || line[0] == '#') pn--;
12290 /* skip positions before number pn */
12291 if (fgets(line, MSG_SIZ, f) == NULL) {
12293 DisplayError(_("Position not found in file"), 0);
12296 if (fenMode || line[0] == '#') pn--;
12301 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12302 DisplayError(_("Bad FEN position in file"), 0);
12306 (void) fgets(line, MSG_SIZ, f);
12307 (void) fgets(line, MSG_SIZ, f);
12309 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12310 (void) fgets(line, MSG_SIZ, f);
12311 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12314 initial_position[i][j++] = CharToPiece(*p);
12318 blackPlaysFirst = FALSE;
12320 (void) fgets(line, MSG_SIZ, f);
12321 if (strncmp(line, "black", strlen("black"))==0)
12322 blackPlaysFirst = TRUE;
12325 startedFromSetupPosition = TRUE;
12327 CopyBoard(boards[0], initial_position);
12328 if (blackPlaysFirst) {
12329 currentMove = forwardMostMove = backwardMostMove = 1;
12330 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12331 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12332 CopyBoard(boards[1], initial_position);
12333 DisplayMessage("", _("Black to play"));
12335 currentMove = forwardMostMove = backwardMostMove = 0;
12336 DisplayMessage("", _("White to play"));
12338 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12339 if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12340 SendToProgram("force\n", &first);
12341 SendBoard(&first, forwardMostMove);
12343 if (appData.debugMode) {
12345 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12346 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12347 fprintf(debugFP, "Load Position\n");
12350 if (positionNumber > 1) {
12351 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12352 DisplayTitle(line);
12354 DisplayTitle(title);
12356 gameMode = EditGame;
12359 timeRemaining[0][1] = whiteTimeRemaining;
12360 timeRemaining[1][1] = blackTimeRemaining;
12361 DrawPosition(FALSE, boards[currentMove]);
12368 CopyPlayerNameIntoFileName (char **dest, char *src)
12370 while (*src != NULLCHAR && *src != ',') {
12375 *(*dest)++ = *src++;
12381 DefaultFileName (char *ext)
12383 static char def[MSG_SIZ];
12386 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12388 CopyPlayerNameIntoFileName(&p, gameInfo.white);
12390 CopyPlayerNameIntoFileName(&p, gameInfo.black);
12392 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12399 /* Save the current game to the given file */
12401 SaveGameToFile (char *filename, int append)
12405 int result, i, t,tot=0;
12407 if (strcmp(filename, "-") == 0) {
12408 return SaveGame(stdout, 0, NULL);
12410 for(i=0; i<10; i++) { // upto 10 tries
12411 f = fopen(filename, append ? "a" : "w");
12412 if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12413 if(f || errno != 13) break;
12414 DoSleep(t = 5 + random()%11); // wait 5-15 msec
12418 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12419 DisplayError(buf, errno);
12422 safeStrCpy(buf, lastMsg, MSG_SIZ);
12423 DisplayMessage(_("Waiting for access to save file"), "");
12424 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12425 DisplayMessage(_("Saving game"), "");
12426 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry...
12427 result = SaveGame(f, 0, NULL);
12428 DisplayMessage(buf, "");
12435 SavePart (char *str)
12437 static char buf[MSG_SIZ];
12440 p = strchr(str, ' ');
12441 if (p == NULL) return str;
12442 strncpy(buf, str, p - str);
12443 buf[p - str] = NULLCHAR;
12447 #define PGN_MAX_LINE 75
12449 #define PGN_SIDE_WHITE 0
12450 #define PGN_SIDE_BLACK 1
12453 FindFirstMoveOutOfBook (int side)
12457 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12458 int index = backwardMostMove;
12459 int has_book_hit = 0;
12461 if( (index % 2) != side ) {
12465 while( index < forwardMostMove ) {
12466 /* Check to see if engine is in book */
12467 int depth = pvInfoList[index].depth;
12468 int score = pvInfoList[index].score;
12474 else if( score == 0 && depth == 63 ) {
12475 in_book = 1; /* Zappa */
12477 else if( score == 2 && depth == 99 ) {
12478 in_book = 1; /* Abrok */
12481 has_book_hit += in_book;
12497 GetOutOfBookInfo (char * buf)
12501 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12503 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12504 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12508 if( oob[0] >= 0 || oob[1] >= 0 ) {
12509 for( i=0; i<2; i++ ) {
12513 if( i > 0 && oob[0] >= 0 ) {
12514 strcat( buf, " " );
12517 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12518 sprintf( buf+strlen(buf), "%s%.2f",
12519 pvInfoList[idx].score >= 0 ? "+" : "",
12520 pvInfoList[idx].score / 100.0 );
12526 /* Save game in PGN style and close the file */
12528 SaveGamePGN (FILE *f)
12530 int i, offset, linelen, newblock;
12534 int movelen, numlen, blank;
12535 char move_buffer[100]; /* [AS] Buffer for move+PV info */
12537 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12539 tm = time((time_t *) NULL);
12541 PrintPGNTags(f, &gameInfo);
12543 if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12545 if (backwardMostMove > 0 || startedFromSetupPosition) {
12546 char *fen = PositionToFEN(backwardMostMove, NULL);
12547 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12548 fprintf(f, "\n{--------------\n");
12549 PrintPosition(f, backwardMostMove);
12550 fprintf(f, "--------------}\n");
12554 /* [AS] Out of book annotation */
12555 if( appData.saveOutOfBookInfo ) {
12558 GetOutOfBookInfo( buf );
12560 if( buf[0] != '\0' ) {
12561 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12568 i = backwardMostMove;
12572 while (i < forwardMostMove) {
12573 /* Print comments preceding this move */
12574 if (commentList[i] != NULL) {
12575 if (linelen > 0) fprintf(f, "\n");
12576 fprintf(f, "%s", commentList[i]);
12581 /* Format move number */
12583 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12586 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12588 numtext[0] = NULLCHAR;
12590 numlen = strlen(numtext);
12593 /* Print move number */
12594 blank = linelen > 0 && numlen > 0;
12595 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12604 fprintf(f, "%s", numtext);
12608 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12609 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12612 blank = linelen > 0 && movelen > 0;
12613 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12622 fprintf(f, "%s", move_buffer);
12623 linelen += movelen;
12625 /* [AS] Add PV info if present */
12626 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12627 /* [HGM] add time */
12628 char buf[MSG_SIZ]; int seconds;
12630 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12636 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12639 seconds = (seconds + 4)/10; // round to full seconds
12641 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12643 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12646 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12647 pvInfoList[i].score >= 0 ? "+" : "",
12648 pvInfoList[i].score / 100.0,
12649 pvInfoList[i].depth,
12652 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12654 /* Print score/depth */
12655 blank = linelen > 0 && movelen > 0;
12656 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12665 fprintf(f, "%s", move_buffer);
12666 linelen += movelen;
12672 /* Start a new line */
12673 if (linelen > 0) fprintf(f, "\n");
12675 /* Print comments after last move */
12676 if (commentList[i] != NULL) {
12677 fprintf(f, "%s\n", commentList[i]);
12681 if (gameInfo.resultDetails != NULL &&
12682 gameInfo.resultDetails[0] != NULLCHAR) {
12683 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12684 PGNResult(gameInfo.result));
12686 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12690 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12694 /* Save game in old style and close the file */
12696 SaveGameOldStyle (FILE *f)
12701 tm = time((time_t *) NULL);
12703 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12706 if (backwardMostMove > 0 || startedFromSetupPosition) {
12707 fprintf(f, "\n[--------------\n");
12708 PrintPosition(f, backwardMostMove);
12709 fprintf(f, "--------------]\n");
12714 i = backwardMostMove;
12715 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12717 while (i < forwardMostMove) {
12718 if (commentList[i] != NULL) {
12719 fprintf(f, "[%s]\n", commentList[i]);
12722 if ((i % 2) == 1) {
12723 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
12726 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
12728 if (commentList[i] != NULL) {
12732 if (i >= forwardMostMove) {
12736 fprintf(f, "%s\n", parseList[i]);
12741 if (commentList[i] != NULL) {
12742 fprintf(f, "[%s]\n", commentList[i]);
12745 /* This isn't really the old style, but it's close enough */
12746 if (gameInfo.resultDetails != NULL &&
12747 gameInfo.resultDetails[0] != NULLCHAR) {
12748 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12749 gameInfo.resultDetails);
12751 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12758 /* Save the current game to open file f and close the file */
12760 SaveGame (FILE *f, int dummy, char *dummy2)
12762 if (gameMode == EditPosition) EditPositionDone(TRUE);
12763 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12764 if (appData.oldSaveStyle)
12765 return SaveGameOldStyle(f);
12767 return SaveGamePGN(f);
12770 /* Save the current position to the given file */
12772 SavePositionToFile (char *filename)
12777 if (strcmp(filename, "-") == 0) {
12778 return SavePosition(stdout, 0, NULL);
12780 f = fopen(filename, "a");
12782 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12783 DisplayError(buf, errno);
12786 safeStrCpy(buf, lastMsg, MSG_SIZ);
12787 DisplayMessage(_("Waiting for access to save file"), "");
12788 flock(fileno(f), LOCK_EX); // [HGM] lock
12789 DisplayMessage(_("Saving position"), "");
12790 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12791 SavePosition(f, 0, NULL);
12792 DisplayMessage(buf, "");
12798 /* Save the current position to the given open file and close the file */
12800 SavePosition (FILE *f, int dummy, char *dummy2)
12805 if (gameMode == EditPosition) EditPositionDone(TRUE);
12806 if (appData.oldSaveStyle) {
12807 tm = time((time_t *) NULL);
12809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12811 fprintf(f, "[--------------\n");
12812 PrintPosition(f, currentMove);
12813 fprintf(f, "--------------]\n");
12815 fen = PositionToFEN(currentMove, NULL);
12816 fprintf(f, "%s\n", fen);
12824 ReloadCmailMsgEvent (int unregister)
12827 static char *inFilename = NULL;
12828 static char *outFilename;
12830 struct stat inbuf, outbuf;
12833 /* Any registered moves are unregistered if unregister is set, */
12834 /* i.e. invoked by the signal handler */
12836 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12837 cmailMoveRegistered[i] = FALSE;
12838 if (cmailCommentList[i] != NULL) {
12839 free(cmailCommentList[i]);
12840 cmailCommentList[i] = NULL;
12843 nCmailMovesRegistered = 0;
12846 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12847 cmailResult[i] = CMAIL_NOT_RESULT;
12851 if (inFilename == NULL) {
12852 /* Because the filenames are static they only get malloced once */
12853 /* and they never get freed */
12854 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12855 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12857 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12858 sprintf(outFilename, "%s.out", appData.cmailGameName);
12861 status = stat(outFilename, &outbuf);
12863 cmailMailedMove = FALSE;
12865 status = stat(inFilename, &inbuf);
12866 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12869 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12870 counts the games, notes how each one terminated, etc.
12872 It would be nice to remove this kludge and instead gather all
12873 the information while building the game list. (And to keep it
12874 in the game list nodes instead of having a bunch of fixed-size
12875 parallel arrays.) Note this will require getting each game's
12876 termination from the PGN tags, as the game list builder does
12877 not process the game moves. --mann
12879 cmailMsgLoaded = TRUE;
12880 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12882 /* Load first game in the file or popup game menu */
12883 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12885 #endif /* !WIN32 */
12893 char string[MSG_SIZ];
12895 if ( cmailMailedMove
12896 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12897 return TRUE; /* Allow free viewing */
12900 /* Unregister move to ensure that we don't leave RegisterMove */
12901 /* with the move registered when the conditions for registering no */
12903 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12904 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12905 nCmailMovesRegistered --;
12907 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12909 free(cmailCommentList[lastLoadGameNumber - 1]);
12910 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12914 if (cmailOldMove == -1) {
12915 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12919 if (currentMove > cmailOldMove + 1) {
12920 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12924 if (currentMove < cmailOldMove) {
12925 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12929 if (forwardMostMove > currentMove) {
12930 /* Silently truncate extra moves */
12934 if ( (currentMove == cmailOldMove + 1)
12935 || ( (currentMove == cmailOldMove)
12936 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12937 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12938 if (gameInfo.result != GameUnfinished) {
12939 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12942 if (commentList[currentMove] != NULL) {
12943 cmailCommentList[lastLoadGameNumber - 1]
12944 = StrSave(commentList[currentMove]);
12946 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12948 if (appData.debugMode)
12949 fprintf(debugFP, "Saving %s for game %d\n",
12950 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12952 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12954 f = fopen(string, "w");
12955 if (appData.oldSaveStyle) {
12956 SaveGameOldStyle(f); /* also closes the file */
12958 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12959 f = fopen(string, "w");
12960 SavePosition(f, 0, NULL); /* also closes the file */
12962 fprintf(f, "{--------------\n");
12963 PrintPosition(f, currentMove);
12964 fprintf(f, "--------------}\n\n");
12966 SaveGame(f, 0, NULL); /* also closes the file*/
12969 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12970 nCmailMovesRegistered ++;
12971 } else if (nCmailGames == 1) {
12972 DisplayError(_("You have not made a move yet"), 0);
12983 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12984 FILE *commandOutput;
12985 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12986 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12992 if (! cmailMsgLoaded) {
12993 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12997 if (nCmailGames == nCmailResults) {
12998 DisplayError(_("No unfinished games"), 0);
13002 #if CMAIL_PROHIBIT_REMAIL
13003 if (cmailMailedMove) {
13004 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);
13005 DisplayError(msg, 0);
13010 if (! (cmailMailedMove || RegisterMove())) return;
13012 if ( cmailMailedMove
13013 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13014 snprintf(string, MSG_SIZ, partCommandString,
13015 appData.debugMode ? " -v" : "", appData.cmailGameName);
13016 commandOutput = popen(string, "r");
13018 if (commandOutput == NULL) {
13019 DisplayError(_("Failed to invoke cmail"), 0);
13021 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13022 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13024 if (nBuffers > 1) {
13025 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13026 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13027 nBytes = MSG_SIZ - 1;
13029 (void) memcpy(msg, buffer, nBytes);
13031 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13033 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13034 cmailMailedMove = TRUE; /* Prevent >1 moves */
13037 for (i = 0; i < nCmailGames; i ++) {
13038 if (cmailResult[i] == CMAIL_NOT_RESULT) {
13043 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13045 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13047 appData.cmailGameName,
13049 LoadGameFromFile(buffer, 1, buffer, FALSE);
13050 cmailMsgLoaded = FALSE;
13054 DisplayInformation(msg);
13055 pclose(commandOutput);
13058 if ((*cmailMsg) != '\0') {
13059 DisplayInformation(cmailMsg);
13064 #endif /* !WIN32 */
13073 int prependComma = 0;
13075 char string[MSG_SIZ]; /* Space for game-list */
13078 if (!cmailMsgLoaded) return "";
13080 if (cmailMailedMove) {
13081 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13083 /* Create a list of games left */
13084 snprintf(string, MSG_SIZ, "[");
13085 for (i = 0; i < nCmailGames; i ++) {
13086 if (! ( cmailMoveRegistered[i]
13087 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13088 if (prependComma) {
13089 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13091 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13095 strcat(string, number);
13098 strcat(string, "]");
13100 if (nCmailMovesRegistered + nCmailResults == 0) {
13101 switch (nCmailGames) {
13103 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13107 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13111 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13116 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13118 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13123 if (nCmailResults == nCmailGames) {
13124 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13126 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13131 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13143 if (gameMode == Training)
13144 SetTrainingModeOff();
13147 cmailMsgLoaded = FALSE;
13148 if (appData.icsActive) {
13149 SendToICS(ics_prefix);
13150 SendToICS("refresh\n");
13155 ExitEvent (int status)
13159 /* Give up on clean exit */
13163 /* Keep trying for clean exit */
13167 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13169 if (telnetISR != NULL) {
13170 RemoveInputSource(telnetISR);
13172 if (icsPR != NoProc) {
13173 DestroyChildProcess(icsPR, TRUE);
13176 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13177 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13179 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13180 /* make sure this other one finishes before killing it! */
13181 if(endingGame) { int count = 0;
13182 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13183 while(endingGame && count++ < 10) DoSleep(1);
13184 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13187 /* Kill off chess programs */
13188 if (first.pr != NoProc) {
13191 DoSleep( appData.delayBeforeQuit );
13192 SendToProgram("quit\n", &first);
13193 DoSleep( appData.delayAfterQuit );
13194 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13196 if (second.pr != NoProc) {
13197 DoSleep( appData.delayBeforeQuit );
13198 SendToProgram("quit\n", &second);
13199 DoSleep( appData.delayAfterQuit );
13200 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13202 if (first.isr != NULL) {
13203 RemoveInputSource(first.isr);
13205 if (second.isr != NULL) {
13206 RemoveInputSource(second.isr);
13209 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13210 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13212 ShutDownFrontEnd();
13219 if (appData.debugMode)
13220 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13224 if (gameMode == MachinePlaysWhite ||
13225 gameMode == MachinePlaysBlack) {
13228 DisplayBothClocks();
13230 if (gameMode == PlayFromGameFile) {
13231 if (appData.timeDelay >= 0)
13232 AutoPlayGameLoop();
13233 } else if (gameMode == IcsExamining && pauseExamInvalid) {
13234 Reset(FALSE, TRUE);
13235 SendToICS(ics_prefix);
13236 SendToICS("refresh\n");
13237 } else if (currentMove < forwardMostMove) {
13238 ForwardInner(forwardMostMove);
13240 pauseExamInvalid = FALSE;
13242 switch (gameMode) {
13246 pauseExamForwardMostMove = forwardMostMove;
13247 pauseExamInvalid = FALSE;
13250 case IcsPlayingWhite:
13251 case IcsPlayingBlack:
13255 case PlayFromGameFile:
13256 (void) StopLoadGameTimer();
13260 case BeginningOfGame:
13261 if (appData.icsActive) return;
13262 /* else fall through */
13263 case MachinePlaysWhite:
13264 case MachinePlaysBlack:
13265 case TwoMachinesPlay:
13266 if (forwardMostMove == 0)
13267 return; /* don't pause if no one has moved */
13268 if ((gameMode == MachinePlaysWhite &&
13269 !WhiteOnMove(forwardMostMove)) ||
13270 (gameMode == MachinePlaysBlack &&
13271 WhiteOnMove(forwardMostMove))) {
13282 EditCommentEvent ()
13284 char title[MSG_SIZ];
13286 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13287 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13289 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13290 WhiteOnMove(currentMove - 1) ? " " : ".. ",
13291 parseList[currentMove - 1]);
13294 EditCommentPopUp(currentMove, title, commentList[currentMove]);
13301 char *tags = PGNTags(&gameInfo);
13303 EditTagsPopUp(tags, NULL);
13308 AnalyzeModeEvent ()
13310 if (appData.noChessProgram || gameMode == AnalyzeMode)
13313 if (gameMode != AnalyzeFile) {
13314 if (!appData.icsEngineAnalyze) {
13316 if (gameMode != EditGame) return;
13318 ResurrectChessProgram();
13319 SendToProgram("analyze\n", &first);
13320 first.analyzing = TRUE;
13321 /*first.maybeThinking = TRUE;*/
13322 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13323 EngineOutputPopUp();
13325 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13330 StartAnalysisClock();
13331 GetTimeMark(&lastNodeCountTime);
13336 AnalyzeFileEvent ()
13338 if (appData.noChessProgram || gameMode == AnalyzeFile)
13341 if (gameMode != AnalyzeMode) {
13343 if (gameMode != EditGame) return;
13344 ResurrectChessProgram();
13345 SendToProgram("analyze\n", &first);
13346 first.analyzing = TRUE;
13347 /*first.maybeThinking = TRUE;*/
13348 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13349 EngineOutputPopUp();
13351 gameMode = AnalyzeFile;
13356 StartAnalysisClock();
13357 GetTimeMark(&lastNodeCountTime);
13359 if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13363 MachineWhiteEvent ()
13366 char *bookHit = NULL;
13368 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13372 if (gameMode == PlayFromGameFile ||
13373 gameMode == TwoMachinesPlay ||
13374 gameMode == Training ||
13375 gameMode == AnalyzeMode ||
13376 gameMode == EndOfGame)
13379 if (gameMode == EditPosition)
13380 EditPositionDone(TRUE);
13382 if (!WhiteOnMove(currentMove)) {
13383 DisplayError(_("It is not White's turn"), 0);
13387 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13390 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13391 gameMode == AnalyzeFile)
13394 ResurrectChessProgram(); /* in case it isn't running */
13395 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13396 gameMode = MachinePlaysWhite;
13399 gameMode = MachinePlaysWhite;
13403 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13405 if (first.sendName) {
13406 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13407 SendToProgram(buf, &first);
13409 if (first.sendTime) {
13410 if (first.useColors) {
13411 SendToProgram("black\n", &first); /*gnu kludge*/
13413 SendTimeRemaining(&first, TRUE);
13415 if (first.useColors) {
13416 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13418 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13419 SetMachineThinkingEnables();
13420 first.maybeThinking = TRUE;
13424 if (appData.autoFlipView && !flipView) {
13425 flipView = !flipView;
13426 DrawPosition(FALSE, NULL);
13427 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13430 if(bookHit) { // [HGM] book: simulate book reply
13431 static char bookMove[MSG_SIZ]; // a bit generous?
13433 programStats.nodes = programStats.depth = programStats.time =
13434 programStats.score = programStats.got_only_move = 0;
13435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13437 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13438 strcat(bookMove, bookHit);
13439 HandleMachineMove(bookMove, &first);
13444 MachineBlackEvent ()
13447 char *bookHit = NULL;
13449 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13453 if (gameMode == PlayFromGameFile ||
13454 gameMode == TwoMachinesPlay ||
13455 gameMode == Training ||
13456 gameMode == AnalyzeMode ||
13457 gameMode == EndOfGame)
13460 if (gameMode == EditPosition)
13461 EditPositionDone(TRUE);
13463 if (WhiteOnMove(currentMove)) {
13464 DisplayError(_("It is not Black's turn"), 0);
13468 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13471 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13472 gameMode == AnalyzeFile)
13475 ResurrectChessProgram(); /* in case it isn't running */
13476 gameMode = MachinePlaysBlack;
13480 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13482 if (first.sendName) {
13483 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13484 SendToProgram(buf, &first);
13486 if (first.sendTime) {
13487 if (first.useColors) {
13488 SendToProgram("white\n", &first); /*gnu kludge*/
13490 SendTimeRemaining(&first, FALSE);
13492 if (first.useColors) {
13493 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13495 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13496 SetMachineThinkingEnables();
13497 first.maybeThinking = TRUE;
13500 if (appData.autoFlipView && flipView) {
13501 flipView = !flipView;
13502 DrawPosition(FALSE, NULL);
13503 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
13505 if(bookHit) { // [HGM] book: simulate book reply
13506 static char bookMove[MSG_SIZ]; // a bit generous?
13508 programStats.nodes = programStats.depth = programStats.time =
13509 programStats.score = programStats.got_only_move = 0;
13510 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13512 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13513 strcat(bookMove, bookHit);
13514 HandleMachineMove(bookMove, &first);
13520 DisplayTwoMachinesTitle ()
13523 if (appData.matchGames > 0) {
13524 if(appData.tourneyFile[0]) {
13525 snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13526 gameInfo.white, _("vs."), gameInfo.black,
13527 nextGame+1, appData.matchGames+1,
13528 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13530 if (first.twoMachinesColor[0] == 'w') {
13531 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13532 gameInfo.white, _("vs."), gameInfo.black,
13533 first.matchWins, second.matchWins,
13534 matchGame - 1 - (first.matchWins + second.matchWins));
13536 snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13537 gameInfo.white, _("vs."), gameInfo.black,
13538 second.matchWins, first.matchWins,
13539 matchGame - 1 - (first.matchWins + second.matchWins));
13542 snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13548 SettingsMenuIfReady ()
13550 if (second.lastPing != second.lastPong) {
13551 DisplayMessage("", _("Waiting for second chess program"));
13552 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13556 DisplayMessage("", "");
13557 SettingsPopUp(&second);
13561 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13564 if (cps->pr == NoProc) {
13565 StartChessProgram(cps);
13566 if (cps->protocolVersion == 1) {
13569 /* kludge: allow timeout for initial "feature" command */
13571 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13572 DisplayMessage("", buf);
13573 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13581 TwoMachinesEvent P((void))
13585 ChessProgramState *onmove;
13586 char *bookHit = NULL;
13587 static int stalling = 0;
13591 if (appData.noChessProgram) return;
13593 if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13594 DisplayError("second engine does not play this", 0);
13598 switch (gameMode) {
13599 case TwoMachinesPlay:
13601 case MachinePlaysWhite:
13602 case MachinePlaysBlack:
13603 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13604 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13608 case BeginningOfGame:
13609 case PlayFromGameFile:
13612 if (gameMode != EditGame) return;
13615 EditPositionDone(TRUE);
13626 // forwardMostMove = currentMove;
13627 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13629 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13631 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13632 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13633 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13637 InitChessProgram(&second, FALSE); // unbalances ping of second engine
13638 SendToProgram("force\n", &second);
13640 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13643 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13644 if(appData.matchPause>10000 || appData.matchPause<10)
13645 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13646 wait = SubtractTimeMarks(&now, &pauseStart);
13647 if(wait < appData.matchPause) {
13648 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13651 // we are now committed to starting the game
13653 DisplayMessage("", "");
13654 if (startedFromSetupPosition) {
13655 SendBoard(&second, backwardMostMove);
13656 if (appData.debugMode) {
13657 fprintf(debugFP, "Two Machines\n");
13660 for (i = backwardMostMove; i < forwardMostMove; i++) {
13661 SendMoveToProgram(i, &second);
13664 gameMode = TwoMachinesPlay;
13666 ModeHighlight(); // [HGM] logo: this triggers display update of logos
13668 DisplayTwoMachinesTitle();
13670 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13675 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13676 SendToProgram(first.computerString, &first);
13677 if (first.sendName) {
13678 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13679 SendToProgram(buf, &first);
13681 SendToProgram(second.computerString, &second);
13682 if (second.sendName) {
13683 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13684 SendToProgram(buf, &second);
13688 if (!first.sendTime || !second.sendTime) {
13689 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13690 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13692 if (onmove->sendTime) {
13693 if (onmove->useColors) {
13694 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13696 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13698 if (onmove->useColors) {
13699 SendToProgram(onmove->twoMachinesColor, onmove);
13701 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13702 // SendToProgram("go\n", onmove);
13703 onmove->maybeThinking = TRUE;
13704 SetMachineThinkingEnables();
13708 if(bookHit) { // [HGM] book: simulate book reply
13709 static char bookMove[MSG_SIZ]; // a bit generous?
13711 programStats.nodes = programStats.depth = programStats.time =
13712 programStats.score = programStats.got_only_move = 0;
13713 sprintf(programStats.movelist, "%s (xbook)", bookHit);
13715 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13716 strcat(bookMove, bookHit);
13717 savedMessage = bookMove; // args for deferred call
13718 savedState = onmove;
13719 ScheduleDelayedEvent(DeferredBookMove, 1);
13726 if (gameMode == Training) {
13727 SetTrainingModeOff();
13728 gameMode = PlayFromGameFile;
13729 DisplayMessage("", _("Training mode off"));
13731 gameMode = Training;
13732 animateTraining = appData.animate;
13734 /* make sure we are not already at the end of the game */
13735 if (currentMove < forwardMostMove) {
13736 SetTrainingModeOn();
13737 DisplayMessage("", _("Training mode on"));
13739 gameMode = PlayFromGameFile;
13740 DisplayError(_("Already at end of game"), 0);
13749 if (!appData.icsActive) return;
13750 switch (gameMode) {
13751 case IcsPlayingWhite:
13752 case IcsPlayingBlack:
13755 case BeginningOfGame:
13763 EditPositionDone(TRUE);
13776 gameMode = IcsIdle;
13786 switch (gameMode) {
13788 SetTrainingModeOff();
13790 case MachinePlaysWhite:
13791 case MachinePlaysBlack:
13792 case BeginningOfGame:
13793 SendToProgram("force\n", &first);
13794 SetUserThinkingEnables();
13796 case PlayFromGameFile:
13797 (void) StopLoadGameTimer();
13798 if (gameFileFP != NULL) {
13803 EditPositionDone(TRUE);
13808 SendToProgram("force\n", &first);
13810 case TwoMachinesPlay:
13811 GameEnds(EndOfFile, NULL, GE_PLAYER);
13812 ResurrectChessProgram();
13813 SetUserThinkingEnables();
13816 ResurrectChessProgram();
13818 case IcsPlayingBlack:
13819 case IcsPlayingWhite:
13820 DisplayError(_("Warning: You are still playing a game"), 0);
13823 DisplayError(_("Warning: You are still observing a game"), 0);
13826 DisplayError(_("Warning: You are still examining a game"), 0);
13837 first.offeredDraw = second.offeredDraw = 0;
13839 if (gameMode == PlayFromGameFile) {
13840 whiteTimeRemaining = timeRemaining[0][currentMove];
13841 blackTimeRemaining = timeRemaining[1][currentMove];
13845 if (gameMode == MachinePlaysWhite ||
13846 gameMode == MachinePlaysBlack ||
13847 gameMode == TwoMachinesPlay ||
13848 gameMode == EndOfGame) {
13849 i = forwardMostMove;
13850 while (i > currentMove) {
13851 SendToProgram("undo\n", &first);
13854 if(!adjustedClock) {
13855 whiteTimeRemaining = timeRemaining[0][currentMove];
13856 blackTimeRemaining = timeRemaining[1][currentMove];
13857 DisplayBothClocks();
13859 if (whiteFlag || blackFlag) {
13860 whiteFlag = blackFlag = 0;
13865 gameMode = EditGame;
13872 EditPositionEvent ()
13874 if (gameMode == EditPosition) {
13880 if (gameMode != EditGame) return;
13882 gameMode = EditPosition;
13885 if (currentMove > 0)
13886 CopyBoard(boards[0], boards[currentMove]);
13888 blackPlaysFirst = !WhiteOnMove(currentMove);
13890 currentMove = forwardMostMove = backwardMostMove = 0;
13891 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13893 if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13899 /* [DM] icsEngineAnalyze - possible call from other functions */
13900 if (appData.icsEngineAnalyze) {
13901 appData.icsEngineAnalyze = FALSE;
13903 DisplayMessage("",_("Close ICS engine analyze..."));
13905 if (first.analysisSupport && first.analyzing) {
13906 SendToProgram("exit\n", &first);
13907 first.analyzing = FALSE;
13909 thinkOutput[0] = NULLCHAR;
13913 EditPositionDone (Boolean fakeRights)
13915 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13917 startedFromSetupPosition = TRUE;
13918 InitChessProgram(&first, FALSE);
13919 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13920 boards[0][EP_STATUS] = EP_NONE;
13921 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13922 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13923 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13924 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13925 } else boards[0][CASTLING][2] = NoRights;
13926 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13927 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13928 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13929 } else boards[0][CASTLING][5] = NoRights;
13931 SendToProgram("force\n", &first);
13932 if (blackPlaysFirst) {
13933 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13934 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13935 currentMove = forwardMostMove = backwardMostMove = 1;
13936 CopyBoard(boards[1], boards[0]);
13938 currentMove = forwardMostMove = backwardMostMove = 0;
13940 SendBoard(&first, forwardMostMove);
13941 if (appData.debugMode) {
13942 fprintf(debugFP, "EditPosDone\n");
13945 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13946 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13947 gameMode = EditGame;
13949 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13950 ClearHighlights(); /* [AS] */
13953 /* Pause for `ms' milliseconds */
13954 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13956 TimeDelay (long ms)
13963 } while (SubtractTimeMarks(&m2, &m1) < ms);
13966 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13968 SendMultiLineToICS (char *buf)
13970 char temp[MSG_SIZ+1], *p;
13977 strncpy(temp, buf, len);
13982 if (*p == '\n' || *p == '\r')
13987 strcat(temp, "\n");
13989 SendToPlayer(temp, strlen(temp));
13993 SetWhiteToPlayEvent ()
13995 if (gameMode == EditPosition) {
13996 blackPlaysFirst = FALSE;
13997 DisplayBothClocks(); /* works because currentMove is 0 */
13998 } else if (gameMode == IcsExamining) {
13999 SendToICS(ics_prefix);
14000 SendToICS("tomove white\n");
14005 SetBlackToPlayEvent ()
14007 if (gameMode == EditPosition) {
14008 blackPlaysFirst = TRUE;
14009 currentMove = 1; /* kludge */
14010 DisplayBothClocks();
14012 } else if (gameMode == IcsExamining) {
14013 SendToICS(ics_prefix);
14014 SendToICS("tomove black\n");
14019 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14022 ChessSquare piece = boards[0][y][x];
14024 if (gameMode != EditPosition && gameMode != IcsExamining) return;
14026 switch (selection) {
14028 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14029 SendToICS(ics_prefix);
14030 SendToICS("bsetup clear\n");
14031 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14032 SendToICS(ics_prefix);
14033 SendToICS("clearboard\n");
14035 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14036 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14037 for (y = 0; y < BOARD_HEIGHT; y++) {
14038 if (gameMode == IcsExamining) {
14039 if (boards[currentMove][y][x] != EmptySquare) {
14040 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14045 boards[0][y][x] = p;
14050 if (gameMode == EditPosition) {
14051 DrawPosition(FALSE, boards[0]);
14056 SetWhiteToPlayEvent();
14060 SetBlackToPlayEvent();
14064 if (gameMode == IcsExamining) {
14065 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14066 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14069 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14070 if(x == BOARD_LEFT-2) {
14071 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14072 boards[0][y][1] = 0;
14074 if(x == BOARD_RGHT+1) {
14075 if(y >= gameInfo.holdingsSize) break;
14076 boards[0][y][BOARD_WIDTH-2] = 0;
14079 boards[0][y][x] = EmptySquare;
14080 DrawPosition(FALSE, boards[0]);
14085 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14086 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
14087 selection = (ChessSquare) (PROMOTED piece);
14088 } else if(piece == EmptySquare) selection = WhiteSilver;
14089 else selection = (ChessSquare)((int)piece - 1);
14093 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14094 piece > (int)BlackMan && piece <= (int)BlackKing ) {
14095 selection = (ChessSquare) (DEMOTED piece);
14096 } else if(piece == EmptySquare) selection = BlackSilver;
14097 else selection = (ChessSquare)((int)piece + 1);
14102 if(gameInfo.variant == VariantShatranj ||
14103 gameInfo.variant == VariantXiangqi ||
14104 gameInfo.variant == VariantCourier ||
14105 gameInfo.variant == VariantMakruk )
14106 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14111 if(gameInfo.variant == VariantXiangqi)
14112 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14113 if(gameInfo.variant == VariantKnightmate)
14114 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14117 if (gameMode == IcsExamining) {
14118 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14119 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14120 PieceToChar(selection), AAA + x, ONE + y);
14123 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14125 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14126 n = PieceToNumber(selection - BlackPawn);
14127 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14128 boards[0][BOARD_HEIGHT-1-n][0] = selection;
14129 boards[0][BOARD_HEIGHT-1-n][1]++;
14131 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14132 n = PieceToNumber(selection);
14133 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14134 boards[0][n][BOARD_WIDTH-1] = selection;
14135 boards[0][n][BOARD_WIDTH-2]++;
14138 boards[0][y][x] = selection;
14139 DrawPosition(TRUE, boards[0]);
14141 fromX = fromY = -1;
14149 DropMenuEvent (ChessSquare selection, int x, int y)
14151 ChessMove moveType;
14153 switch (gameMode) {
14154 case IcsPlayingWhite:
14155 case MachinePlaysBlack:
14156 if (!WhiteOnMove(currentMove)) {
14157 DisplayMoveError(_("It is Black's turn"));
14160 moveType = WhiteDrop;
14162 case IcsPlayingBlack:
14163 case MachinePlaysWhite:
14164 if (WhiteOnMove(currentMove)) {
14165 DisplayMoveError(_("It is White's turn"));
14168 moveType = BlackDrop;
14171 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14177 if (moveType == BlackDrop && selection < BlackPawn) {
14178 selection = (ChessSquare) ((int) selection
14179 + (int) BlackPawn - (int) WhitePawn);
14181 if (boards[currentMove][y][x] != EmptySquare) {
14182 DisplayMoveError(_("That square is occupied"));
14186 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14192 /* Accept a pending offer of any kind from opponent */
14194 if (appData.icsActive) {
14195 SendToICS(ics_prefix);
14196 SendToICS("accept\n");
14197 } else if (cmailMsgLoaded) {
14198 if (currentMove == cmailOldMove &&
14199 commentList[cmailOldMove] != NULL &&
14200 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14201 "Black offers a draw" : "White offers a draw")) {
14203 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14204 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14206 DisplayError(_("There is no pending offer on this move"), 0);
14207 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14210 /* Not used for offers from chess program */
14217 /* Decline a pending offer of any kind from opponent */
14219 if (appData.icsActive) {
14220 SendToICS(ics_prefix);
14221 SendToICS("decline\n");
14222 } else if (cmailMsgLoaded) {
14223 if (currentMove == cmailOldMove &&
14224 commentList[cmailOldMove] != NULL &&
14225 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14226 "Black offers a draw" : "White offers a draw")) {
14228 AppendComment(cmailOldMove, "Draw declined", TRUE);
14229 DisplayComment(cmailOldMove - 1, "Draw declined");
14232 DisplayError(_("There is no pending offer on this move"), 0);
14235 /* Not used for offers from chess program */
14242 /* Issue ICS rematch command */
14243 if (appData.icsActive) {
14244 SendToICS(ics_prefix);
14245 SendToICS("rematch\n");
14252 /* Call your opponent's flag (claim a win on time) */
14253 if (appData.icsActive) {
14254 SendToICS(ics_prefix);
14255 SendToICS("flag\n");
14257 switch (gameMode) {
14260 case MachinePlaysWhite:
14263 GameEnds(GameIsDrawn, "Both players ran out of time",
14266 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14268 DisplayError(_("Your opponent is not out of time"), 0);
14271 case MachinePlaysBlack:
14274 GameEnds(GameIsDrawn, "Both players ran out of time",
14277 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14279 DisplayError(_("Your opponent is not out of time"), 0);
14287 ClockClick (int which)
14288 { // [HGM] code moved to back-end from winboard.c
14289 if(which) { // black clock
14290 if (gameMode == EditPosition || gameMode == IcsExamining) {
14291 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14292 SetBlackToPlayEvent();
14293 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14294 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14295 } else if (shiftKey) {
14296 AdjustClock(which, -1);
14297 } else if (gameMode == IcsPlayingWhite ||
14298 gameMode == MachinePlaysBlack) {
14301 } else { // white clock
14302 if (gameMode == EditPosition || gameMode == IcsExamining) {
14303 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14304 SetWhiteToPlayEvent();
14305 } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14306 UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14307 } else if (shiftKey) {
14308 AdjustClock(which, -1);
14309 } else if (gameMode == IcsPlayingBlack ||
14310 gameMode == MachinePlaysWhite) {
14319 /* Offer draw or accept pending draw offer from opponent */
14321 if (appData.icsActive) {
14322 /* Note: tournament rules require draw offers to be
14323 made after you make your move but before you punch
14324 your clock. Currently ICS doesn't let you do that;
14325 instead, you immediately punch your clock after making
14326 a move, but you can offer a draw at any time. */
14328 SendToICS(ics_prefix);
14329 SendToICS("draw\n");
14330 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14331 } else if (cmailMsgLoaded) {
14332 if (currentMove == cmailOldMove &&
14333 commentList[cmailOldMove] != NULL &&
14334 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14335 "Black offers a draw" : "White offers a draw")) {
14336 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14337 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14338 } else if (currentMove == cmailOldMove + 1) {
14339 char *offer = WhiteOnMove(cmailOldMove) ?
14340 "White offers a draw" : "Black offers a draw";
14341 AppendComment(currentMove, offer, TRUE);
14342 DisplayComment(currentMove - 1, offer);
14343 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14345 DisplayError(_("You must make your move before offering a draw"), 0);
14346 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14348 } else if (first.offeredDraw) {
14349 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14351 if (first.sendDrawOffers) {
14352 SendToProgram("draw\n", &first);
14353 userOfferedDraw = TRUE;
14361 /* Offer Adjourn or accept pending Adjourn offer from opponent */
14363 if (appData.icsActive) {
14364 SendToICS(ics_prefix);
14365 SendToICS("adjourn\n");
14367 /* Currently GNU Chess doesn't offer or accept Adjourns */
14375 /* Offer Abort or accept pending Abort offer from opponent */
14377 if (appData.icsActive) {
14378 SendToICS(ics_prefix);
14379 SendToICS("abort\n");
14381 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14388 /* Resign. You can do this even if it's not your turn. */
14390 if (appData.icsActive) {
14391 SendToICS(ics_prefix);
14392 SendToICS("resign\n");
14394 switch (gameMode) {
14395 case MachinePlaysWhite:
14396 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14398 case MachinePlaysBlack:
14399 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14402 if (cmailMsgLoaded) {
14404 if (WhiteOnMove(cmailOldMove)) {
14405 GameEnds(BlackWins, "White resigns", GE_PLAYER);
14407 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14409 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14420 StopObservingEvent ()
14422 /* Stop observing current games */
14423 SendToICS(ics_prefix);
14424 SendToICS("unobserve\n");
14428 StopExaminingEvent ()
14430 /* Stop observing current game */
14431 SendToICS(ics_prefix);
14432 SendToICS("unexamine\n");
14436 ForwardInner (int target)
14438 int limit; int oldSeekGraphUp = seekGraphUp;
14440 if (appData.debugMode)
14441 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14442 target, currentMove, forwardMostMove);
14444 if (gameMode == EditPosition)
14447 seekGraphUp = FALSE;
14448 MarkTargetSquares(1);
14450 if (gameMode == PlayFromGameFile && !pausing)
14453 if (gameMode == IcsExamining && pausing)
14454 limit = pauseExamForwardMostMove;
14456 limit = forwardMostMove;
14458 if (target > limit) target = limit;
14460 if (target > 0 && moveList[target - 1][0]) {
14461 int fromX, fromY, toX, toY;
14462 toX = moveList[target - 1][2] - AAA;
14463 toY = moveList[target - 1][3] - ONE;
14464 if (moveList[target - 1][1] == '@') {
14465 if (appData.highlightLastMove) {
14466 SetHighlights(-1, -1, toX, toY);
14469 fromX = moveList[target - 1][0] - AAA;
14470 fromY = moveList[target - 1][1] - ONE;
14471 if (target == currentMove + 1) {
14472 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14474 if (appData.highlightLastMove) {
14475 SetHighlights(fromX, fromY, toX, toY);
14479 if (gameMode == EditGame || gameMode == AnalyzeMode ||
14480 gameMode == Training || gameMode == PlayFromGameFile ||
14481 gameMode == AnalyzeFile) {
14482 while (currentMove < target) {
14483 SendMoveToProgram(currentMove++, &first);
14486 currentMove = target;
14489 if (gameMode == EditGame || gameMode == EndOfGame) {
14490 whiteTimeRemaining = timeRemaining[0][currentMove];
14491 blackTimeRemaining = timeRemaining[1][currentMove];
14493 DisplayBothClocks();
14494 DisplayMove(currentMove - 1);
14495 DrawPosition(oldSeekGraphUp, boards[currentMove]);
14496 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14497 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14498 DisplayComment(currentMove - 1, commentList[currentMove]);
14500 ClearMap(); // [HGM] exclude: invalidate map
14507 if (gameMode == IcsExamining && !pausing) {
14508 SendToICS(ics_prefix);
14509 SendToICS("forward\n");
14511 ForwardInner(currentMove + 1);
14518 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14519 /* to optimze, we temporarily turn off analysis mode while we feed
14520 * the remaining moves to the engine. Otherwise we get analysis output
14523 if (first.analysisSupport) {
14524 SendToProgram("exit\nforce\n", &first);
14525 first.analyzing = FALSE;
14529 if (gameMode == IcsExamining && !pausing) {
14530 SendToICS(ics_prefix);
14531 SendToICS("forward 999999\n");
14533 ForwardInner(forwardMostMove);
14536 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14537 /* we have fed all the moves, so reactivate analysis mode */
14538 SendToProgram("analyze\n", &first);
14539 first.analyzing = TRUE;
14540 /*first.maybeThinking = TRUE;*/
14541 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14546 BackwardInner (int target)
14548 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14550 if (appData.debugMode)
14551 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14552 target, currentMove, forwardMostMove);
14554 if (gameMode == EditPosition) return;
14555 seekGraphUp = FALSE;
14556 MarkTargetSquares(1);
14557 if (currentMove <= backwardMostMove) {
14559 DrawPosition(full_redraw, boards[currentMove]);
14562 if (gameMode == PlayFromGameFile && !pausing)
14565 if (moveList[target][0]) {
14566 int fromX, fromY, toX, toY;
14567 toX = moveList[target][2] - AAA;
14568 toY = moveList[target][3] - ONE;
14569 if (moveList[target][1] == '@') {
14570 if (appData.highlightLastMove) {
14571 SetHighlights(-1, -1, toX, toY);
14574 fromX = moveList[target][0] - AAA;
14575 fromY = moveList[target][1] - ONE;
14576 if (target == currentMove - 1) {
14577 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14579 if (appData.highlightLastMove) {
14580 SetHighlights(fromX, fromY, toX, toY);
14584 if (gameMode == EditGame || gameMode==AnalyzeMode ||
14585 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14586 while (currentMove > target) {
14587 if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14588 // null move cannot be undone. Reload program with move history before it.
14590 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14591 if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14593 SendBoard(&first, i);
14594 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14597 SendToProgram("undo\n", &first);
14601 currentMove = target;
14604 if (gameMode == EditGame || gameMode == EndOfGame) {
14605 whiteTimeRemaining = timeRemaining[0][currentMove];
14606 blackTimeRemaining = timeRemaining[1][currentMove];
14608 DisplayBothClocks();
14609 DisplayMove(currentMove - 1);
14610 DrawPosition(full_redraw, boards[currentMove]);
14611 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14612 // [HGM] PV info: routine tests if comment empty
14613 DisplayComment(currentMove - 1, commentList[currentMove]);
14614 ClearMap(); // [HGM] exclude: invalidate map
14620 if (gameMode == IcsExamining && !pausing) {
14621 SendToICS(ics_prefix);
14622 SendToICS("backward\n");
14624 BackwardInner(currentMove - 1);
14631 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14632 /* to optimize, we temporarily turn off analysis mode while we undo
14633 * all the moves. Otherwise we get analysis output after each undo.
14635 if (first.analysisSupport) {
14636 SendToProgram("exit\nforce\n", &first);
14637 first.analyzing = FALSE;
14641 if (gameMode == IcsExamining && !pausing) {
14642 SendToICS(ics_prefix);
14643 SendToICS("backward 999999\n");
14645 BackwardInner(backwardMostMove);
14648 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14649 /* we have fed all the moves, so reactivate analysis mode */
14650 SendToProgram("analyze\n", &first);
14651 first.analyzing = TRUE;
14652 /*first.maybeThinking = TRUE;*/
14653 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14660 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14661 if (to >= forwardMostMove) to = forwardMostMove;
14662 if (to <= backwardMostMove) to = backwardMostMove;
14663 if (to < currentMove) {
14671 RevertEvent (Boolean annotate)
14673 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14676 if (gameMode != IcsExamining) {
14677 DisplayError(_("You are not examining a game"), 0);
14681 DisplayError(_("You can't revert while pausing"), 0);
14684 SendToICS(ics_prefix);
14685 SendToICS("revert\n");
14689 RetractMoveEvent ()
14691 switch (gameMode) {
14692 case MachinePlaysWhite:
14693 case MachinePlaysBlack:
14694 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14695 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14698 if (forwardMostMove < 2) return;
14699 currentMove = forwardMostMove = forwardMostMove - 2;
14700 whiteTimeRemaining = timeRemaining[0][currentMove];
14701 blackTimeRemaining = timeRemaining[1][currentMove];
14702 DisplayBothClocks();
14703 DisplayMove(currentMove - 1);
14704 ClearHighlights();/*!! could figure this out*/
14705 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14706 SendToProgram("remove\n", &first);
14707 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14710 case BeginningOfGame:
14714 case IcsPlayingWhite:
14715 case IcsPlayingBlack:
14716 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14717 SendToICS(ics_prefix);
14718 SendToICS("takeback 2\n");
14720 SendToICS(ics_prefix);
14721 SendToICS("takeback 1\n");
14730 ChessProgramState *cps;
14732 switch (gameMode) {
14733 case MachinePlaysWhite:
14734 if (!WhiteOnMove(forwardMostMove)) {
14735 DisplayError(_("It is your turn"), 0);
14740 case MachinePlaysBlack:
14741 if (WhiteOnMove(forwardMostMove)) {
14742 DisplayError(_("It is your turn"), 0);
14747 case TwoMachinesPlay:
14748 if (WhiteOnMove(forwardMostMove) ==
14749 (first.twoMachinesColor[0] == 'w')) {
14755 case BeginningOfGame:
14759 SendToProgram("?\n", cps);
14763 TruncateGameEvent ()
14766 if (gameMode != EditGame) return;
14773 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14774 if (forwardMostMove > currentMove) {
14775 if (gameInfo.resultDetails != NULL) {
14776 free(gameInfo.resultDetails);
14777 gameInfo.resultDetails = NULL;
14778 gameInfo.result = GameUnfinished;
14780 forwardMostMove = currentMove;
14781 HistorySet(parseList, backwardMostMove, forwardMostMove,
14789 if (appData.noChessProgram) return;
14790 switch (gameMode) {
14791 case MachinePlaysWhite:
14792 if (WhiteOnMove(forwardMostMove)) {
14793 DisplayError(_("Wait until your turn"), 0);
14797 case BeginningOfGame:
14798 case MachinePlaysBlack:
14799 if (!WhiteOnMove(forwardMostMove)) {
14800 DisplayError(_("Wait until your turn"), 0);
14805 DisplayError(_("No hint available"), 0);
14808 SendToProgram("hint\n", &first);
14809 hintRequested = TRUE;
14815 if (appData.noChessProgram) return;
14816 switch (gameMode) {
14817 case MachinePlaysWhite:
14818 if (WhiteOnMove(forwardMostMove)) {
14819 DisplayError(_("Wait until your turn"), 0);
14823 case BeginningOfGame:
14824 case MachinePlaysBlack:
14825 if (!WhiteOnMove(forwardMostMove)) {
14826 DisplayError(_("Wait until your turn"), 0);
14831 EditPositionDone(TRUE);
14833 case TwoMachinesPlay:
14838 SendToProgram("bk\n", &first);
14839 bookOutput[0] = NULLCHAR;
14840 bookRequested = TRUE;
14846 char *tags = PGNTags(&gameInfo);
14847 TagsPopUp(tags, CmailMsg());
14851 /* end button procedures */
14854 PrintPosition (FILE *fp, int move)
14858 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14859 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14860 char c = PieceToChar(boards[move][i][j]);
14861 fputc(c == 'x' ? '.' : c, fp);
14862 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14865 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14866 fprintf(fp, "white to play\n");
14868 fprintf(fp, "black to play\n");
14872 PrintOpponents (FILE *fp)
14874 if (gameInfo.white != NULL) {
14875 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14881 /* Find last component of program's own name, using some heuristics */
14883 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14886 int local = (strcmp(host, "localhost") == 0);
14887 while (!local && (p = strchr(prog, ';')) != NULL) {
14889 while (*p == ' ') p++;
14892 if (*prog == '"' || *prog == '\'') {
14893 q = strchr(prog + 1, *prog);
14895 q = strchr(prog, ' ');
14897 if (q == NULL) q = prog + strlen(prog);
14899 while (p >= prog && *p != '/' && *p != '\\') p--;
14901 if(p == prog && *p == '"') p++;
14903 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14904 memcpy(buf, p, q - p);
14905 buf[q - p] = NULLCHAR;
14913 TimeControlTagValue ()
14916 if (!appData.clockMode) {
14917 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14918 } else if (movesPerSession > 0) {
14919 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14920 } else if (timeIncrement == 0) {
14921 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14923 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14925 return StrSave(buf);
14931 /* This routine is used only for certain modes */
14932 VariantClass v = gameInfo.variant;
14933 ChessMove r = GameUnfinished;
14936 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14937 r = gameInfo.result;
14938 p = gameInfo.resultDetails;
14939 gameInfo.resultDetails = NULL;
14941 ClearGameInfo(&gameInfo);
14942 gameInfo.variant = v;
14944 switch (gameMode) {
14945 case MachinePlaysWhite:
14946 gameInfo.event = StrSave( appData.pgnEventHeader );
14947 gameInfo.site = StrSave(HostName());
14948 gameInfo.date = PGNDate();
14949 gameInfo.round = StrSave("-");
14950 gameInfo.white = StrSave(first.tidy);
14951 gameInfo.black = StrSave(UserName());
14952 gameInfo.timeControl = TimeControlTagValue();
14955 case MachinePlaysBlack:
14956 gameInfo.event = StrSave( appData.pgnEventHeader );
14957 gameInfo.site = StrSave(HostName());
14958 gameInfo.date = PGNDate();
14959 gameInfo.round = StrSave("-");
14960 gameInfo.white = StrSave(UserName());
14961 gameInfo.black = StrSave(first.tidy);
14962 gameInfo.timeControl = TimeControlTagValue();
14965 case TwoMachinesPlay:
14966 gameInfo.event = StrSave( appData.pgnEventHeader );
14967 gameInfo.site = StrSave(HostName());
14968 gameInfo.date = PGNDate();
14971 snprintf(buf, MSG_SIZ, "%d", roundNr);
14972 gameInfo.round = StrSave(buf);
14974 gameInfo.round = StrSave("-");
14976 if (first.twoMachinesColor[0] == 'w') {
14977 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14978 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14980 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14981 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14983 gameInfo.timeControl = TimeControlTagValue();
14987 gameInfo.event = StrSave("Edited game");
14988 gameInfo.site = StrSave(HostName());
14989 gameInfo.date = PGNDate();
14990 gameInfo.round = StrSave("-");
14991 gameInfo.white = StrSave("-");
14992 gameInfo.black = StrSave("-");
14993 gameInfo.result = r;
14994 gameInfo.resultDetails = p;
14998 gameInfo.event = StrSave("Edited position");
14999 gameInfo.site = StrSave(HostName());
15000 gameInfo.date = PGNDate();
15001 gameInfo.round = StrSave("-");
15002 gameInfo.white = StrSave("-");
15003 gameInfo.black = StrSave("-");
15006 case IcsPlayingWhite:
15007 case IcsPlayingBlack:
15012 case PlayFromGameFile:
15013 gameInfo.event = StrSave("Game from non-PGN file");
15014 gameInfo.site = StrSave(HostName());
15015 gameInfo.date = PGNDate();
15016 gameInfo.round = StrSave("-");
15017 gameInfo.white = StrSave("?");
15018 gameInfo.black = StrSave("?");
15027 ReplaceComment (int index, char *text)
15033 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15034 pvInfoList[index-1].depth == len &&
15035 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15036 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15037 while (*text == '\n') text++;
15038 len = strlen(text);
15039 while (len > 0 && text[len - 1] == '\n') len--;
15041 if (commentList[index] != NULL)
15042 free(commentList[index]);
15045 commentList[index] = NULL;
15048 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15049 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15050 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15051 commentList[index] = (char *) malloc(len + 2);
15052 strncpy(commentList[index], text, len);
15053 commentList[index][len] = '\n';
15054 commentList[index][len + 1] = NULLCHAR;
15056 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15058 commentList[index] = (char *) malloc(len + 7);
15059 safeStrCpy(commentList[index], "{\n", 3);
15060 safeStrCpy(commentList[index]+2, text, len+1);
15061 commentList[index][len+2] = NULLCHAR;
15062 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15063 strcat(commentList[index], "\n}\n");
15068 CrushCRs (char *text)
15076 if (ch == '\r') continue;
15078 } while (ch != '\0');
15082 AppendComment (int index, char *text, Boolean addBraces)
15083 /* addBraces tells if we should add {} */
15088 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15089 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15092 while (*text == '\n') text++;
15093 len = strlen(text);
15094 while (len > 0 && text[len - 1] == '\n') len--;
15095 text[len] = NULLCHAR;
15097 if (len == 0) return;
15099 if (commentList[index] != NULL) {
15100 Boolean addClosingBrace = addBraces;
15101 old = commentList[index];
15102 oldlen = strlen(old);
15103 while(commentList[index][oldlen-1] == '\n')
15104 commentList[index][--oldlen] = NULLCHAR;
15105 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15106 safeStrCpy(commentList[index], old, oldlen + len + 6);
15108 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15109 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15110 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15111 while (*text == '\n') { text++; len--; }
15112 commentList[index][--oldlen] = NULLCHAR;
15114 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15115 else strcat(commentList[index], "\n");
15116 strcat(commentList[index], text);
15117 if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15118 else strcat(commentList[index], "\n");
15120 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15122 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15123 else commentList[index][0] = NULLCHAR;
15124 strcat(commentList[index], text);
15125 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15126 if(addBraces == TRUE) strcat(commentList[index], "}\n");
15131 FindStr (char * text, char * sub_text)
15133 char * result = strstr( text, sub_text );
15135 if( result != NULL ) {
15136 result += strlen( sub_text );
15142 /* [AS] Try to extract PV info from PGN comment */
15143 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15145 GetInfoFromComment (int index, char * text)
15147 char * sep = text, *p;
15149 if( text != NULL && index > 0 ) {
15152 int time = -1, sec = 0, deci;
15153 char * s_eval = FindStr( text, "[%eval " );
15154 char * s_emt = FindStr( text, "[%emt " );
15156 if( s_eval != NULL || s_emt != NULL ) {
15160 if( s_eval != NULL ) {
15161 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15165 if( delim != ']' ) {
15170 if( s_emt != NULL ) {
15175 /* We expect something like: [+|-]nnn.nn/dd */
15178 if(*text != '{') return text; // [HGM] braces: must be normal comment
15180 sep = strchr( text, '/' );
15181 if( sep == NULL || sep < (text+4) ) {
15186 if(p[1] == '(') { // comment starts with PV
15187 p = strchr(p, ')'); // locate end of PV
15188 if(p == NULL || sep < p+5) return text;
15189 // at this point we have something like "{(.*) +0.23/6 ..."
15190 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15191 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15192 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15194 time = -1; sec = -1; deci = -1;
15195 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15196 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15197 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15198 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
15202 if( score_lo < 0 || score_lo >= 100 ) {
15206 if(sec >= 0) time = 600*time + 10*sec; else
15207 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15209 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15211 /* [HGM] PV time: now locate end of PV info */
15212 while( *++sep >= '0' && *sep <= '9'); // strip depth
15214 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15216 while( *++sep >= '0' && *sep <= '9'); // strip seconds
15218 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15219 while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15230 pvInfoList[index-1].depth = depth;
15231 pvInfoList[index-1].score = score;
15232 pvInfoList[index-1].time = 10*time; // centi-sec
15233 if(*sep == '}') *sep = 0; else *--sep = '{';
15234 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15240 SendToProgram (char *message, ChessProgramState *cps)
15242 int count, outCount, error;
15245 if (cps->pr == NoProc) return;
15248 if (appData.debugMode) {
15251 fprintf(debugFP, "%ld >%-6s: %s",
15252 SubtractTimeMarks(&now, &programStartTime),
15253 cps->which, message);
15255 fprintf(serverFP, "%ld >%-6s: %s",
15256 SubtractTimeMarks(&now, &programStartTime),
15257 cps->which, message), fflush(serverFP);
15260 count = strlen(message);
15261 outCount = OutputToProcess(cps->pr, message, count, &error);
15262 if (outCount < count && !exiting
15263 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15264 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15265 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15266 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15267 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15268 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15269 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15270 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15272 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15273 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15274 gameInfo.result = res;
15276 gameInfo.resultDetails = StrSave(buf);
15278 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15279 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15284 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15288 ChessProgramState *cps = (ChessProgramState *)closure;
15290 if (isr != cps->isr) return; /* Killed intentionally */
15293 RemoveInputSource(cps->isr);
15294 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15295 _(cps->which), cps->program);
15296 if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15297 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15298 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15299 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15300 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15301 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15303 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15304 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15305 gameInfo.result = res;
15307 gameInfo.resultDetails = StrSave(buf);
15309 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15310 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15312 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15313 _(cps->which), cps->program);
15314 RemoveInputSource(cps->isr);
15316 /* [AS] Program is misbehaving badly... kill it */
15317 if( count == -2 ) {
15318 DestroyChildProcess( cps->pr, 9 );
15322 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15327 if ((end_str = strchr(message, '\r')) != NULL)
15328 *end_str = NULLCHAR;
15329 if ((end_str = strchr(message, '\n')) != NULL)
15330 *end_str = NULLCHAR;
15332 if (appData.debugMode) {
15333 TimeMark now; int print = 1;
15334 char *quote = ""; char c; int i;
15336 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15337 char start = message[0];
15338 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15339 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15340 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
15341 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15342 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15343 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
15344 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15345 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
15346 sscanf(message, "hint: %c", &c)!=1 &&
15347 sscanf(message, "pong %c", &c)!=1 && start != '#') {
15348 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15349 print = (appData.engineComments >= 2);
15351 message[0] = start; // restore original message
15355 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15356 SubtractTimeMarks(&now, &programStartTime), cps->which,
15360 fprintf(serverFP, "%ld <%-6s: %s%s\n",
15361 SubtractTimeMarks(&now, &programStartTime), cps->which,
15363 message), fflush(serverFP);
15367 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15368 if (appData.icsEngineAnalyze) {
15369 if (strstr(message, "whisper") != NULL ||
15370 strstr(message, "kibitz") != NULL ||
15371 strstr(message, "tellics") != NULL) return;
15374 HandleMachineMove(message, cps);
15379 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15384 if( timeControl_2 > 0 ) {
15385 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15386 tc = timeControl_2;
15389 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15390 inc /= cps->timeOdds;
15391 st /= cps->timeOdds;
15393 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15396 /* Set exact time per move, normally using st command */
15397 if (cps->stKludge) {
15398 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15400 if (seconds == 0) {
15401 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15403 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15406 snprintf(buf, MSG_SIZ, "st %d\n", st);
15409 /* Set conventional or incremental time control, using level command */
15410 if (seconds == 0) {
15411 /* Note old gnuchess bug -- minutes:seconds used to not work.
15412 Fixed in later versions, but still avoid :seconds
15413 when seconds is 0. */
15414 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15416 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15417 seconds, inc/1000.);
15420 SendToProgram(buf, cps);
15422 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15423 /* Orthogonally, limit search to given depth */
15425 if (cps->sdKludge) {
15426 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15428 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15430 SendToProgram(buf, cps);
15433 if(cps->nps >= 0) { /* [HGM] nps */
15434 if(cps->supportsNPS == FALSE)
15435 cps->nps = -1; // don't use if engine explicitly says not supported!
15437 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15438 SendToProgram(buf, cps);
15443 ChessProgramState *
15445 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15447 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15448 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15454 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15456 char message[MSG_SIZ];
15459 /* Note: this routine must be called when the clocks are stopped
15460 or when they have *just* been set or switched; otherwise
15461 it will be off by the time since the current tick started.
15463 if (machineWhite) {
15464 time = whiteTimeRemaining / 10;
15465 otime = blackTimeRemaining / 10;
15467 time = blackTimeRemaining / 10;
15468 otime = whiteTimeRemaining / 10;
15470 /* [HGM] translate opponent's time by time-odds factor */
15471 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15473 if (time <= 0) time = 1;
15474 if (otime <= 0) otime = 1;
15476 snprintf(message, MSG_SIZ, "time %ld\n", time);
15477 SendToProgram(message, cps);
15479 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15480 SendToProgram(message, cps);
15484 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15487 int len = strlen(name);
15490 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15492 sscanf(*p, "%d", &val);
15494 while (**p && **p != ' ')
15496 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15497 SendToProgram(buf, cps);
15504 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15507 int len = strlen(name);
15508 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15510 sscanf(*p, "%d", loc);
15511 while (**p && **p != ' ') (*p)++;
15512 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15513 SendToProgram(buf, cps);
15520 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15523 int len = strlen(name);
15524 if (strncmp((*p), name, len) == 0
15525 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15527 sscanf(*p, "%[^\"]", loc);
15528 while (**p && **p != '\"') (*p)++;
15529 if (**p == '\"') (*p)++;
15530 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15531 SendToProgram(buf, cps);
15538 ParseOption (Option *opt, ChessProgramState *cps)
15539 // [HGM] options: process the string that defines an engine option, and determine
15540 // name, type, default value, and allowed value range
15542 char *p, *q, buf[MSG_SIZ];
15543 int n, min = (-1)<<31, max = 1<<31, def;
15545 if(p = strstr(opt->name, " -spin ")) {
15546 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15547 if(max < min) max = min; // enforce consistency
15548 if(def < min) def = min;
15549 if(def > max) def = max;
15554 } else if((p = strstr(opt->name, " -slider "))) {
15555 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15556 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15557 if(max < min) max = min; // enforce consistency
15558 if(def < min) def = min;
15559 if(def > max) def = max;
15563 opt->type = Spin; // Slider;
15564 } else if((p = strstr(opt->name, " -string "))) {
15565 opt->textValue = p+9;
15566 opt->type = TextBox;
15567 } else if((p = strstr(opt->name, " -file "))) {
15568 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15569 opt->textValue = p+7;
15570 opt->type = FileName; // FileName;
15571 } else if((p = strstr(opt->name, " -path "))) {
15572 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15573 opt->textValue = p+7;
15574 opt->type = PathName; // PathName;
15575 } else if(p = strstr(opt->name, " -check ")) {
15576 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15577 opt->value = (def != 0);
15578 opt->type = CheckBox;
15579 } else if(p = strstr(opt->name, " -combo ")) {
15580 opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15581 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15582 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15583 opt->value = n = 0;
15584 while(q = StrStr(q, " /// ")) {
15585 n++; *q = 0; // count choices, and null-terminate each of them
15587 if(*q == '*') { // remember default, which is marked with * prefix
15591 cps->comboList[cps->comboCnt++] = q;
15593 cps->comboList[cps->comboCnt++] = NULL;
15595 opt->type = ComboBox;
15596 } else if(p = strstr(opt->name, " -button")) {
15597 opt->type = Button;
15598 } else if(p = strstr(opt->name, " -save")) {
15599 opt->type = SaveButton;
15600 } else return FALSE;
15601 *p = 0; // terminate option name
15602 // now look if the command-line options define a setting for this engine option.
15603 if(cps->optionSettings && cps->optionSettings[0])
15604 p = strstr(cps->optionSettings, opt->name); else p = NULL;
15605 if(p && (p == cps->optionSettings || p[-1] == ',')) {
15606 snprintf(buf, MSG_SIZ, "option %s", p);
15607 if(p = strstr(buf, ",")) *p = 0;
15608 if(q = strchr(buf, '=')) switch(opt->type) {
15610 for(n=0; n<opt->max; n++)
15611 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15614 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15618 opt->value = atoi(q+1);
15623 SendToProgram(buf, cps);
15629 FeatureDone (ChessProgramState *cps, int val)
15631 DelayedEventCallback cb = GetDelayedEvent();
15632 if ((cb == InitBackEnd3 && cps == &first) ||
15633 (cb == SettingsMenuIfReady && cps == &second) ||
15634 (cb == LoadEngine) ||
15635 (cb == TwoMachinesEventIfReady)) {
15636 CancelDelayedEvent();
15637 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15639 cps->initDone = val;
15642 /* Parse feature command from engine */
15644 ParseFeatures (char *args, ChessProgramState *cps)
15652 while (*p == ' ') p++;
15653 if (*p == NULLCHAR) return;
15655 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15656 if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15657 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15658 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15659 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15660 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15661 if (BoolFeature(&p, "reuse", &val, cps)) {
15662 /* Engine can disable reuse, but can't enable it if user said no */
15663 if (!val) cps->reuse = FALSE;
15666 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15667 if (StringFeature(&p, "myname", cps->tidy, cps)) {
15668 if (gameMode == TwoMachinesPlay) {
15669 DisplayTwoMachinesTitle();
15675 if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15676 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15677 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15678 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15679 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15680 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15681 if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15682 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15683 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15684 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15685 if (IntFeature(&p, "done", &val, cps)) {
15686 FeatureDone(cps, val);
15689 /* Added by Tord: */
15690 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15691 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15692 /* End of additions by Tord */
15694 /* [HGM] added features: */
15695 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15696 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15697 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15698 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15699 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15700 if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15701 if (StringFeature(&p, "option", buf, cps)) {
15702 FREE(cps->option[cps->nrOptions].name);
15703 cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15704 safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15705 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15706 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15707 SendToProgram(buf, cps);
15710 if(cps->nrOptions >= MAX_OPTIONS) {
15712 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15713 DisplayError(buf, 0);
15717 /* End of additions by HGM */
15719 /* unknown feature: complain and skip */
15721 while (*q && *q != '=') q++;
15722 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15723 SendToProgram(buf, cps);
15729 while (*p && *p != '\"') p++;
15730 if (*p == '\"') p++;
15732 while (*p && *p != ' ') p++;
15740 PeriodicUpdatesEvent (int newState)
15742 if (newState == appData.periodicUpdates)
15745 appData.periodicUpdates=newState;
15747 /* Display type changes, so update it now */
15748 // DisplayAnalysis();
15750 /* Get the ball rolling again... */
15752 AnalysisPeriodicEvent(1);
15753 StartAnalysisClock();
15758 PonderNextMoveEvent (int newState)
15760 if (newState == appData.ponderNextMove) return;
15761 if (gameMode == EditPosition) EditPositionDone(TRUE);
15763 SendToProgram("hard\n", &first);
15764 if (gameMode == TwoMachinesPlay) {
15765 SendToProgram("hard\n", &second);
15768 SendToProgram("easy\n", &first);
15769 thinkOutput[0] = NULLCHAR;
15770 if (gameMode == TwoMachinesPlay) {
15771 SendToProgram("easy\n", &second);
15774 appData.ponderNextMove = newState;
15778 NewSettingEvent (int option, int *feature, char *command, int value)
15782 if (gameMode == EditPosition) EditPositionDone(TRUE);
15783 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15784 if(feature == NULL || *feature) SendToProgram(buf, &first);
15785 if (gameMode == TwoMachinesPlay) {
15786 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15791 ShowThinkingEvent ()
15792 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15794 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15795 int newState = appData.showThinking
15796 // [HGM] thinking: other features now need thinking output as well
15797 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15799 if (oldState == newState) return;
15800 oldState = newState;
15801 if (gameMode == EditPosition) EditPositionDone(TRUE);
15803 SendToProgram("post\n", &first);
15804 if (gameMode == TwoMachinesPlay) {
15805 SendToProgram("post\n", &second);
15808 SendToProgram("nopost\n", &first);
15809 thinkOutput[0] = NULLCHAR;
15810 if (gameMode == TwoMachinesPlay) {
15811 SendToProgram("nopost\n", &second);
15814 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15818 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15820 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15821 if (pr == NoProc) return;
15822 AskQuestion(title, question, replyPrefix, pr);
15826 TypeInEvent (char firstChar)
15828 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15829 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15830 gameMode == AnalyzeMode || gameMode == EditGame ||
15831 gameMode == EditPosition || gameMode == IcsExamining ||
15832 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15833 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15834 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15835 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15836 gameMode == Training) PopUpMoveDialog(firstChar);
15840 TypeInDoneEvent (char *move)
15843 int n, fromX, fromY, toX, toY;
15845 ChessMove moveType;
15848 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15849 EditPositionPasteFEN(move);
15852 // [HGM] movenum: allow move number to be typed in any mode
15853 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15857 // undocumented kludge: allow command-line option to be typed in!
15858 // (potentially fatal, and does not implement the effect of the option.)
15859 // should only be used for options that are values on which future decisions will be made,
15860 // and definitely not on options that would be used during initialization.
15861 if(strstr(move, "!!! -") == move) {
15862 ParseArgsFromString(move+4);
15866 if (gameMode != EditGame && currentMove != forwardMostMove &&
15867 gameMode != Training) {
15868 DisplayMoveError(_("Displayed move is not current"));
15870 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15871 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15872 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15873 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15874 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15875 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15877 DisplayMoveError(_("Could not parse move"));
15883 DisplayMove (int moveNumber)
15885 char message[MSG_SIZ];
15887 char cpThinkOutput[MSG_SIZ];
15889 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15891 if (moveNumber == forwardMostMove - 1 ||
15892 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15894 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15896 if (strchr(cpThinkOutput, '\n')) {
15897 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15900 *cpThinkOutput = NULLCHAR;
15903 /* [AS] Hide thinking from human user */
15904 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15905 *cpThinkOutput = NULLCHAR;
15906 if( thinkOutput[0] != NULLCHAR ) {
15909 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15910 cpThinkOutput[i] = '.';
15912 cpThinkOutput[i] = NULLCHAR;
15913 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15917 if (moveNumber == forwardMostMove - 1 &&
15918 gameInfo.resultDetails != NULL) {
15919 if (gameInfo.resultDetails[0] == NULLCHAR) {
15920 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15922 snprintf(res, MSG_SIZ, " {%s} %s",
15923 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15929 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15930 DisplayMessage(res, cpThinkOutput);
15932 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15933 WhiteOnMove(moveNumber) ? " " : ".. ",
15934 parseList[moveNumber], res);
15935 DisplayMessage(message, cpThinkOutput);
15940 DisplayComment (int moveNumber, char *text)
15942 char title[MSG_SIZ];
15944 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15945 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15947 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15948 WhiteOnMove(moveNumber) ? " " : ".. ",
15949 parseList[moveNumber]);
15951 if (text != NULL && (appData.autoDisplayComment || commentUp))
15952 CommentPopUp(title, text);
15955 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15956 * might be busy thinking or pondering. It can be omitted if your
15957 * gnuchess is configured to stop thinking immediately on any user
15958 * input. However, that gnuchess feature depends on the FIONREAD
15959 * ioctl, which does not work properly on some flavors of Unix.
15962 Attention (ChessProgramState *cps)
15965 if (!cps->useSigint) return;
15966 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15967 switch (gameMode) {
15968 case MachinePlaysWhite:
15969 case MachinePlaysBlack:
15970 case TwoMachinesPlay:
15971 case IcsPlayingWhite:
15972 case IcsPlayingBlack:
15975 /* Skip if we know it isn't thinking */
15976 if (!cps->maybeThinking) return;
15977 if (appData.debugMode)
15978 fprintf(debugFP, "Interrupting %s\n", cps->which);
15979 InterruptChildProcess(cps->pr);
15980 cps->maybeThinking = FALSE;
15985 #endif /*ATTENTION*/
15991 if (whiteTimeRemaining <= 0) {
15994 if (appData.icsActive) {
15995 if (appData.autoCallFlag &&
15996 gameMode == IcsPlayingBlack && !blackFlag) {
15997 SendToICS(ics_prefix);
15998 SendToICS("flag\n");
16002 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16004 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16005 if (appData.autoCallFlag) {
16006 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16013 if (blackTimeRemaining <= 0) {
16016 if (appData.icsActive) {
16017 if (appData.autoCallFlag &&
16018 gameMode == IcsPlayingWhite && !whiteFlag) {
16019 SendToICS(ics_prefix);
16020 SendToICS("flag\n");
16024 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16026 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16027 if (appData.autoCallFlag) {
16028 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16039 CheckTimeControl ()
16041 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16042 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16045 * add time to clocks when time control is achieved ([HGM] now also used for increment)
16047 if ( !WhiteOnMove(forwardMostMove) ) {
16048 /* White made time control */
16049 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16050 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16051 /* [HGM] time odds: correct new time quota for time odds! */
16052 / WhitePlayer()->timeOdds;
16053 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16055 lastBlack -= blackTimeRemaining;
16056 /* Black made time control */
16057 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16058 / WhitePlayer()->other->timeOdds;
16059 lastWhite = whiteTimeRemaining;
16064 DisplayBothClocks ()
16066 int wom = gameMode == EditPosition ?
16067 !blackPlaysFirst : WhiteOnMove(currentMove);
16068 DisplayWhiteClock(whiteTimeRemaining, wom);
16069 DisplayBlackClock(blackTimeRemaining, !wom);
16073 /* Timekeeping seems to be a portability nightmare. I think everyone
16074 has ftime(), but I'm really not sure, so I'm including some ifdefs
16075 to use other calls if you don't. Clocks will be less accurate if
16076 you have neither ftime nor gettimeofday.
16079 /* VS 2008 requires the #include outside of the function */
16080 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16081 #include <sys/timeb.h>
16084 /* Get the current time as a TimeMark */
16086 GetTimeMark (TimeMark *tm)
16088 #if HAVE_GETTIMEOFDAY
16090 struct timeval timeVal;
16091 struct timezone timeZone;
16093 gettimeofday(&timeVal, &timeZone);
16094 tm->sec = (long) timeVal.tv_sec;
16095 tm->ms = (int) (timeVal.tv_usec / 1000L);
16097 #else /*!HAVE_GETTIMEOFDAY*/
16100 // include <sys/timeb.h> / moved to just above start of function
16101 struct timeb timeB;
16104 tm->sec = (long) timeB.time;
16105 tm->ms = (int) timeB.millitm;
16107 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16108 tm->sec = (long) time(NULL);
16114 /* Return the difference in milliseconds between two
16115 time marks. We assume the difference will fit in a long!
16118 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16120 return 1000L*(tm2->sec - tm1->sec) +
16121 (long) (tm2->ms - tm1->ms);
16126 * Code to manage the game clocks.
16128 * In tournament play, black starts the clock and then white makes a move.
16129 * We give the human user a slight advantage if he is playing white---the
16130 * clocks don't run until he makes his first move, so it takes zero time.
16131 * Also, we don't account for network lag, so we could get out of sync
16132 * with GNU Chess's clock -- but then, referees are always right.
16135 static TimeMark tickStartTM;
16136 static long intendedTickLength;
16139 NextTickLength (long timeRemaining)
16141 long nominalTickLength, nextTickLength;
16143 if (timeRemaining > 0L && timeRemaining <= 10000L)
16144 nominalTickLength = 100L;
16146 nominalTickLength = 1000L;
16147 nextTickLength = timeRemaining % nominalTickLength;
16148 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16150 return nextTickLength;
16153 /* Adjust clock one minute up or down */
16155 AdjustClock (Boolean which, int dir)
16157 if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16158 if(which) blackTimeRemaining += 60000*dir;
16159 else whiteTimeRemaining += 60000*dir;
16160 DisplayBothClocks();
16161 adjustedClock = TRUE;
16164 /* Stop clocks and reset to a fresh time control */
16168 (void) StopClockTimer();
16169 if (appData.icsActive) {
16170 whiteTimeRemaining = blackTimeRemaining = 0;
16171 } else if (searchTime) {
16172 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16173 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16174 } else { /* [HGM] correct new time quote for time odds */
16175 whiteTC = blackTC = fullTimeControlString;
16176 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16177 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16179 if (whiteFlag || blackFlag) {
16181 whiteFlag = blackFlag = FALSE;
16183 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16184 DisplayBothClocks();
16185 adjustedClock = FALSE;
16188 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16190 /* Decrement running clock by amount of time that has passed */
16194 long timeRemaining;
16195 long lastTickLength, fudge;
16198 if (!appData.clockMode) return;
16199 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16203 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16205 /* Fudge if we woke up a little too soon */
16206 fudge = intendedTickLength - lastTickLength;
16207 if (fudge < 0 || fudge > FUDGE) fudge = 0;
16209 if (WhiteOnMove(forwardMostMove)) {
16210 if(whiteNPS >= 0) lastTickLength = 0;
16211 timeRemaining = whiteTimeRemaining -= lastTickLength;
16212 if(timeRemaining < 0 && !appData.icsActive) {
16213 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16214 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16215 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16216 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16219 DisplayWhiteClock(whiteTimeRemaining - fudge,
16220 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16222 if(blackNPS >= 0) lastTickLength = 0;
16223 timeRemaining = blackTimeRemaining -= lastTickLength;
16224 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16225 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16227 blackStartMove = forwardMostMove;
16228 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16231 DisplayBlackClock(blackTimeRemaining - fudge,
16232 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16234 if (CheckFlags()) return;
16236 if(twoBoards) { // count down secondary board's clocks as well
16237 activePartnerTime -= lastTickLength;
16239 if(activePartner == 'W')
16240 DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16242 DisplayBlackClock(activePartnerTime, TRUE);
16247 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16248 StartClockTimer(intendedTickLength);
16250 /* if the time remaining has fallen below the alarm threshold, sound the
16251 * alarm. if the alarm has sounded and (due to a takeback or time control
16252 * with increment) the time remaining has increased to a level above the
16253 * threshold, reset the alarm so it can sound again.
16256 if (appData.icsActive && appData.icsAlarm) {
16258 /* make sure we are dealing with the user's clock */
16259 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16260 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16263 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16264 alarmSounded = FALSE;
16265 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16267 alarmSounded = TRUE;
16273 /* A player has just moved, so stop the previously running
16274 clock and (if in clock mode) start the other one.
16275 We redisplay both clocks in case we're in ICS mode, because
16276 ICS gives us an update to both clocks after every move.
16277 Note that this routine is called *after* forwardMostMove
16278 is updated, so the last fractional tick must be subtracted
16279 from the color that is *not* on move now.
16282 SwitchClocks (int newMoveNr)
16284 long lastTickLength;
16286 int flagged = FALSE;
16290 if (StopClockTimer() && appData.clockMode) {
16291 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16292 if (!WhiteOnMove(forwardMostMove)) {
16293 if(blackNPS >= 0) lastTickLength = 0;
16294 blackTimeRemaining -= lastTickLength;
16295 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16296 // if(pvInfoList[forwardMostMove].time == -1)
16297 pvInfoList[forwardMostMove].time = // use GUI time
16298 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16300 if(whiteNPS >= 0) lastTickLength = 0;
16301 whiteTimeRemaining -= lastTickLength;
16302 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16303 // if(pvInfoList[forwardMostMove].time == -1)
16304 pvInfoList[forwardMostMove].time =
16305 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16307 flagged = CheckFlags();
16309 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16310 CheckTimeControl();
16312 if (flagged || !appData.clockMode) return;
16314 switch (gameMode) {
16315 case MachinePlaysBlack:
16316 case MachinePlaysWhite:
16317 case BeginningOfGame:
16318 if (pausing) return;
16322 case PlayFromGameFile:
16330 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16331 if(WhiteOnMove(forwardMostMove))
16332 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16333 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16337 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16338 whiteTimeRemaining : blackTimeRemaining);
16339 StartClockTimer(intendedTickLength);
16343 /* Stop both clocks */
16347 long lastTickLength;
16350 if (!StopClockTimer()) return;
16351 if (!appData.clockMode) return;
16355 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16356 if (WhiteOnMove(forwardMostMove)) {
16357 if(whiteNPS >= 0) lastTickLength = 0;
16358 whiteTimeRemaining -= lastTickLength;
16359 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16361 if(blackNPS >= 0) lastTickLength = 0;
16362 blackTimeRemaining -= lastTickLength;
16363 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16368 /* Start clock of player on move. Time may have been reset, so
16369 if clock is already running, stop and restart it. */
16373 (void) StopClockTimer(); /* in case it was running already */
16374 DisplayBothClocks();
16375 if (CheckFlags()) return;
16377 if (!appData.clockMode) return;
16378 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16380 GetTimeMark(&tickStartTM);
16381 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16382 whiteTimeRemaining : blackTimeRemaining);
16384 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16385 whiteNPS = blackNPS = -1;
16386 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16387 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16388 whiteNPS = first.nps;
16389 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16390 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16391 blackNPS = first.nps;
16392 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16393 whiteNPS = second.nps;
16394 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16395 blackNPS = second.nps;
16396 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16398 StartClockTimer(intendedTickLength);
16402 TimeString (long ms)
16404 long second, minute, hour, day;
16406 static char buf[32];
16408 if (ms > 0 && ms <= 9900) {
16409 /* convert milliseconds to tenths, rounding up */
16410 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16412 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16416 /* convert milliseconds to seconds, rounding up */
16417 /* use floating point to avoid strangeness of integer division
16418 with negative dividends on many machines */
16419 second = (long) floor(((double) (ms + 999L)) / 1000.0);
16426 day = second / (60 * 60 * 24);
16427 second = second % (60 * 60 * 24);
16428 hour = second / (60 * 60);
16429 second = second % (60 * 60);
16430 minute = second / 60;
16431 second = second % 60;
16434 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16435 sign, day, hour, minute, second);
16437 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16439 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16446 * This is necessary because some C libraries aren't ANSI C compliant yet.
16449 StrStr (char *string, char *match)
16453 length = strlen(match);
16455 for (i = strlen(string) - length; i >= 0; i--, string++)
16456 if (!strncmp(match, string, length))
16463 StrCaseStr (char *string, char *match)
16467 length = strlen(match);
16469 for (i = strlen(string) - length; i >= 0; i--, string++) {
16470 for (j = 0; j < length; j++) {
16471 if (ToLower(match[j]) != ToLower(string[j]))
16474 if (j == length) return string;
16482 StrCaseCmp (char *s1, char *s2)
16487 c1 = ToLower(*s1++);
16488 c2 = ToLower(*s2++);
16489 if (c1 > c2) return 1;
16490 if (c1 < c2) return -1;
16491 if (c1 == NULLCHAR) return 0;
16499 return isupper(c) ? tolower(c) : c;
16506 return islower(c) ? toupper(c) : c;
16508 #endif /* !_amigados */
16515 if ((ret = (char *) malloc(strlen(s) + 1)))
16517 safeStrCpy(ret, s, strlen(s)+1);
16523 StrSavePtr (char *s, char **savePtr)
16528 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16529 safeStrCpy(*savePtr, s, strlen(s)+1);
16541 clock = time((time_t *)NULL);
16542 tm = localtime(&clock);
16543 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16544 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16545 return StrSave(buf);
16550 PositionToFEN (int move, char *overrideCastling)
16552 int i, j, fromX, fromY, toX, toY;
16559 whiteToPlay = (gameMode == EditPosition) ?
16560 !blackPlaysFirst : (move % 2 == 0);
16563 /* Piece placement data */
16564 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16565 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16567 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16568 if (boards[move][i][j] == EmptySquare) {
16570 } else { ChessSquare piece = boards[move][i][j];
16571 if (emptycount > 0) {
16572 if(emptycount<10) /* [HGM] can be >= 10 */
16573 *p++ = '0' + emptycount;
16574 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16577 if(PieceToChar(piece) == '+') {
16578 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16580 piece = (ChessSquare)(DEMOTED piece);
16582 *p++ = PieceToChar(piece);
16584 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16585 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16590 if (emptycount > 0) {
16591 if(emptycount<10) /* [HGM] can be >= 10 */
16592 *p++ = '0' + emptycount;
16593 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16600 /* [HGM] print Crazyhouse or Shogi holdings */
16601 if( gameInfo.holdingsWidth ) {
16602 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16604 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16605 piece = boards[move][i][BOARD_WIDTH-1];
16606 if( piece != EmptySquare )
16607 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16608 *p++ = PieceToChar(piece);
16610 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16611 piece = boards[move][BOARD_HEIGHT-i-1][0];
16612 if( piece != EmptySquare )
16613 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16614 *p++ = PieceToChar(piece);
16617 if( q == p ) *p++ = '-';
16623 *p++ = whiteToPlay ? 'w' : 'b';
16626 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16627 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16629 if(nrCastlingRights) {
16631 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16632 /* [HGM] write directly from rights */
16633 if(boards[move][CASTLING][2] != NoRights &&
16634 boards[move][CASTLING][0] != NoRights )
16635 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16636 if(boards[move][CASTLING][2] != NoRights &&
16637 boards[move][CASTLING][1] != NoRights )
16638 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16639 if(boards[move][CASTLING][5] != NoRights &&
16640 boards[move][CASTLING][3] != NoRights )
16641 *p++ = boards[move][CASTLING][3] + AAA;
16642 if(boards[move][CASTLING][5] != NoRights &&
16643 boards[move][CASTLING][4] != NoRights )
16644 *p++ = boards[move][CASTLING][4] + AAA;
16647 /* [HGM] write true castling rights */
16648 if( nrCastlingRights == 6 ) {
16649 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16650 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
16651 if(boards[move][CASTLING][1] == BOARD_LEFT &&
16652 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
16653 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16654 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
16655 if(boards[move][CASTLING][4] == BOARD_LEFT &&
16656 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
16659 if (q == p) *p++ = '-'; /* No castling rights */
16663 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16664 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16665 /* En passant target square */
16666 if (move > backwardMostMove) {
16667 fromX = moveList[move - 1][0] - AAA;
16668 fromY = moveList[move - 1][1] - ONE;
16669 toX = moveList[move - 1][2] - AAA;
16670 toY = moveList[move - 1][3] - ONE;
16671 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16672 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16673 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16675 /* 2-square pawn move just happened */
16677 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16681 } else if(move == backwardMostMove) {
16682 // [HGM] perhaps we should always do it like this, and forget the above?
16683 if((signed char)boards[move][EP_STATUS] >= 0) {
16684 *p++ = boards[move][EP_STATUS] + AAA;
16685 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16696 /* [HGM] find reversible plies */
16697 { int i = 0, j=move;
16699 if (appData.debugMode) { int k;
16700 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16701 for(k=backwardMostMove; k<=forwardMostMove; k++)
16702 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16706 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16707 if( j == backwardMostMove ) i += initialRulePlies;
16708 sprintf(p, "%d ", i);
16709 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16711 /* Fullmove number */
16712 sprintf(p, "%d", (move / 2) + 1);
16714 return StrSave(buf);
16718 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16727 /* [HGM] by default clear Crazyhouse holdings, if present */
16728 if(gameInfo.holdingsWidth) {
16729 for(i=0; i<BOARD_HEIGHT; i++) {
16730 board[i][0] = EmptySquare; /* black holdings */
16731 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16732 board[i][1] = (ChessSquare) 0; /* black counts */
16733 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16737 /* Piece placement data */
16738 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16741 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16742 if (*p == '/') p++;
16743 emptycount = gameInfo.boardWidth - j;
16744 while (emptycount--)
16745 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16747 #if(BOARD_FILES >= 10)
16748 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16749 p++; emptycount=10;
16750 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16751 while (emptycount--)
16752 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16754 } else if (isdigit(*p)) {
16755 emptycount = *p++ - '0';
16756 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16757 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16758 while (emptycount--)
16759 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16760 } else if (*p == '+' || isalpha(*p)) {
16761 if (j >= gameInfo.boardWidth) return FALSE;
16763 piece = CharToPiece(*++p);
16764 if(piece == EmptySquare) return FALSE; /* unknown piece */
16765 piece = (ChessSquare) (PROMOTED piece ); p++;
16766 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16767 } else piece = CharToPiece(*p++);
16769 if(piece==EmptySquare) return FALSE; /* unknown piece */
16770 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16771 piece = (ChessSquare) (PROMOTED piece);
16772 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16775 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16781 while (*p == '/' || *p == ' ') p++;
16783 /* [HGM] look for Crazyhouse holdings here */
16784 while(*p==' ') p++;
16785 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16787 if(*p == '-' ) p++; /* empty holdings */ else {
16788 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16789 /* if we would allow FEN reading to set board size, we would */
16790 /* have to add holdings and shift the board read so far here */
16791 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16793 if((int) piece >= (int) BlackPawn ) {
16794 i = (int)piece - (int)BlackPawn;
16795 i = PieceToNumber((ChessSquare)i);
16796 if( i >= gameInfo.holdingsSize ) return FALSE;
16797 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16798 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16800 i = (int)piece - (int)WhitePawn;
16801 i = PieceToNumber((ChessSquare)i);
16802 if( i >= gameInfo.holdingsSize ) return FALSE;
16803 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16804 board[i][BOARD_WIDTH-2]++; /* black holdings */
16811 while(*p == ' ') p++;
16815 if(appData.colorNickNames) {
16816 if( c == appData.colorNickNames[0] ) c = 'w'; else
16817 if( c == appData.colorNickNames[1] ) c = 'b';
16821 *blackPlaysFirst = FALSE;
16824 *blackPlaysFirst = TRUE;
16830 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16831 /* return the extra info in global variiables */
16833 /* set defaults in case FEN is incomplete */
16834 board[EP_STATUS] = EP_UNKNOWN;
16835 for(i=0; i<nrCastlingRights; i++ ) {
16836 board[CASTLING][i] =
16837 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16838 } /* assume possible unless obviously impossible */
16839 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16840 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16841 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16842 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16843 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16844 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16845 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16846 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16849 while(*p==' ') p++;
16850 if(nrCastlingRights) {
16851 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16852 /* castling indicator present, so default becomes no castlings */
16853 for(i=0; i<nrCastlingRights; i++ ) {
16854 board[CASTLING][i] = NoRights;
16857 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16858 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16859 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16860 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16861 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16863 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16864 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16865 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16867 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16868 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16869 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16870 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16871 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16872 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16875 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16876 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16877 board[CASTLING][2] = whiteKingFile;
16880 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16881 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16882 board[CASTLING][2] = whiteKingFile;
16885 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16886 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16887 board[CASTLING][5] = blackKingFile;
16890 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16891 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16892 board[CASTLING][5] = blackKingFile;
16895 default: /* FRC castlings */
16896 if(c >= 'a') { /* black rights */
16897 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16898 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16899 if(i == BOARD_RGHT) break;
16900 board[CASTLING][5] = i;
16902 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16903 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16905 board[CASTLING][3] = c;
16907 board[CASTLING][4] = c;
16908 } else { /* white rights */
16909 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16910 if(board[0][i] == WhiteKing) break;
16911 if(i == BOARD_RGHT) break;
16912 board[CASTLING][2] = i;
16913 c -= AAA - 'a' + 'A';
16914 if(board[0][c] >= WhiteKing) break;
16916 board[CASTLING][0] = c;
16918 board[CASTLING][1] = c;
16922 for(i=0; i<nrCastlingRights; i++)
16923 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16924 if (appData.debugMode) {
16925 fprintf(debugFP, "FEN castling rights:");
16926 for(i=0; i<nrCastlingRights; i++)
16927 fprintf(debugFP, " %d", board[CASTLING][i]);
16928 fprintf(debugFP, "\n");
16931 while(*p==' ') p++;
16934 /* read e.p. field in games that know e.p. capture */
16935 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16936 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16938 p++; board[EP_STATUS] = EP_NONE;
16940 char c = *p++ - AAA;
16942 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16943 if(*p >= '0' && *p <='9') p++;
16944 board[EP_STATUS] = c;
16949 if(sscanf(p, "%d", &i) == 1) {
16950 FENrulePlies = i; /* 50-move ply counter */
16951 /* (The move number is still ignored) */
16958 EditPositionPasteFEN (char *fen)
16961 Board initial_position;
16963 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16964 DisplayError(_("Bad FEN position in clipboard"), 0);
16967 int savedBlackPlaysFirst = blackPlaysFirst;
16968 EditPositionEvent();
16969 blackPlaysFirst = savedBlackPlaysFirst;
16970 CopyBoard(boards[0], initial_position);
16971 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16972 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16973 DisplayBothClocks();
16974 DrawPosition(FALSE, boards[currentMove]);
16979 static char cseq[12] = "\\ ";
16982 set_cont_sequence (char *new_seq)
16987 // handle bad attempts to set the sequence
16989 return 0; // acceptable error - no debug
16991 len = strlen(new_seq);
16992 ret = (len > 0) && (len < sizeof(cseq));
16994 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16995 else if (appData.debugMode)
16996 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17001 reformat a source message so words don't cross the width boundary. internal
17002 newlines are not removed. returns the wrapped size (no null character unless
17003 included in source message). If dest is NULL, only calculate the size required
17004 for the dest buffer. lp argument indicats line position upon entry, and it's
17005 passed back upon exit.
17008 wrap (char *dest, char *src, int count, int width, int *lp)
17010 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17012 cseq_len = strlen(cseq);
17013 old_line = line = *lp;
17014 ansi = len = clen = 0;
17016 for (i=0; i < count; i++)
17018 if (src[i] == '\033')
17021 // if we hit the width, back up
17022 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17024 // store i & len in case the word is too long
17025 old_i = i, old_len = len;
17027 // find the end of the last word
17028 while (i && src[i] != ' ' && src[i] != '\n')
17034 // word too long? restore i & len before splitting it
17035 if ((old_i-i+clen) >= width)
17042 if (i && src[i-1] == ' ')
17045 if (src[i] != ' ' && src[i] != '\n')
17052 // now append the newline and continuation sequence
17057 strncpy(dest+len, cseq, cseq_len);
17065 dest[len] = src[i];
17069 if (src[i] == '\n')
17074 if (dest && appData.debugMode)
17076 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17077 count, width, line, len, *lp);
17078 show_bytes(debugFP, src, count);
17079 fprintf(debugFP, "\ndest: ");
17080 show_bytes(debugFP, dest, len);
17081 fprintf(debugFP, "\n");
17083 *lp = dest ? line : old_line;
17088 // [HGM] vari: routines for shelving variations
17089 Boolean modeRestore = FALSE;
17092 PushInner (int firstMove, int lastMove)
17094 int i, j, nrMoves = lastMove - firstMove;
17096 // push current tail of game on stack
17097 savedResult[storedGames] = gameInfo.result;
17098 savedDetails[storedGames] = gameInfo.resultDetails;
17099 gameInfo.resultDetails = NULL;
17100 savedFirst[storedGames] = firstMove;
17101 savedLast [storedGames] = lastMove;
17102 savedFramePtr[storedGames] = framePtr;
17103 framePtr -= nrMoves; // reserve space for the boards
17104 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17105 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17106 for(j=0; j<MOVE_LEN; j++)
17107 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17108 for(j=0; j<2*MOVE_LEN; j++)
17109 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17110 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17111 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17112 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17113 pvInfoList[firstMove+i-1].depth = 0;
17114 commentList[framePtr+i] = commentList[firstMove+i];
17115 commentList[firstMove+i] = NULL;
17119 forwardMostMove = firstMove; // truncate game so we can start variation
17123 PushTail (int firstMove, int lastMove)
17125 if(appData.icsActive) { // only in local mode
17126 forwardMostMove = currentMove; // mimic old ICS behavior
17129 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17131 PushInner(firstMove, lastMove);
17132 if(storedGames == 1) GreyRevert(FALSE);
17133 if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17137 PopInner (Boolean annotate)
17140 char buf[8000], moveBuf[20];
17142 ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17143 storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17144 nrMoves = savedLast[storedGames] - currentMove;
17147 if(!WhiteOnMove(currentMove))
17148 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17149 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17150 for(i=currentMove; i<forwardMostMove; i++) {
17152 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17153 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17154 strcat(buf, moveBuf);
17155 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17156 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17160 for(i=1; i<=nrMoves; i++) { // copy last variation back
17161 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17162 for(j=0; j<MOVE_LEN; j++)
17163 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17164 for(j=0; j<2*MOVE_LEN; j++)
17165 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17166 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17167 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17168 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17169 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17170 commentList[currentMove+i] = commentList[framePtr+i];
17171 commentList[framePtr+i] = NULL;
17173 if(annotate) AppendComment(currentMove+1, buf, FALSE);
17174 framePtr = savedFramePtr[storedGames];
17175 gameInfo.result = savedResult[storedGames];
17176 if(gameInfo.resultDetails != NULL) {
17177 free(gameInfo.resultDetails);
17179 gameInfo.resultDetails = savedDetails[storedGames];
17180 forwardMostMove = currentMove + nrMoves;
17184 PopTail (Boolean annotate)
17186 if(appData.icsActive) return FALSE; // only in local mode
17187 if(!storedGames) return FALSE; // sanity
17188 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17190 PopInner(annotate);
17191 if(currentMove < forwardMostMove) ForwardEvent(); else
17192 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17194 if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17200 { // remove all shelved variations
17202 for(i=0; i<storedGames; i++) {
17203 if(savedDetails[i])
17204 free(savedDetails[i]);
17205 savedDetails[i] = NULL;
17207 for(i=framePtr; i<MAX_MOVES; i++) {
17208 if(commentList[i]) free(commentList[i]);
17209 commentList[i] = NULL;
17211 framePtr = MAX_MOVES-1;
17216 LoadVariation (int index, char *text)
17217 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17218 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17219 int level = 0, move;
17221 if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17222 // first find outermost bracketing variation
17223 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17224 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17225 if(*p == '{') wait = '}'; else
17226 if(*p == '[') wait = ']'; else
17227 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17228 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17230 if(*p == wait) wait = NULLCHAR; // closing ]} found
17233 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17234 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17235 end[1] = NULLCHAR; // clip off comment beyond variation
17236 ToNrEvent(currentMove-1);
17237 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17238 // kludge: use ParsePV() to append variation to game
17239 move = currentMove;
17240 ParsePV(start, TRUE, TRUE);
17241 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17242 ClearPremoveHighlights();
17244 ToNrEvent(currentMove+1);